Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send by Signature (ERC777 extension) - transfer without paying for gas #965

Closed
chompomonim opened this issue Apr 2, 2018 · 35 comments
Closed
Labels

Comments

@chompomonim
Copy link

chompomonim commented Apr 2, 2018

Preamble

EIP: 965
Title: Authorize Operator by Cheque (ERC777 extension) - possibility to transfer without paying for gas
Author: Jaro Šatkevič @chompomonim, Anatoly Ressin @artazor
Type: Standard Track
Category: ERC
Status: Draft
Created: 2018-06-05
Requires: 777

Problem

The problem of tokens spending is that token owner have to have ETH in his account to pay for gas. So it's impossible to have pure token account. Even in ERC777 (#777) where you can have operator which can manage your tokens (and paying for gas), there still is same problem of lack of gas to initiate authorizeOperator call.

Solution

Add default operator smart contract which would accept tx with signed permission to send tokens. This tx can be made by anyone who has tokens owner signature and is willing to pay for gas.

Possible implementation:

contract ChequeOperator {
    using SafeMath for uint256;
    using ECRecovery for bytes32;

    struct Agreement {
        uint256 totalPaid;
        address token;
        address payer;
        address beneficiary;
        bytes data;
    }
    mapping(bytes => Agreement) internal agreements;
    mapping(address => mapping(uint256 => bool)) public usedNonces; // For simple sendByCheque
    
    
    /* Simple send by Checque */
    
    function signerOfSimpleCheque(address _token, address _to, uint256 _amount, bytes _data, uint256 _nonce, bytes _sig) private pure returns (address) {
        return keccak256(abi.encodePacked(_token, _to, _amount, _data, _nonce)).toEthSignedMessageHash().recover(_sig);
    }
    
    function sendByCheque(address _token, address _to, uint256 _amount, bytes _data, uint256 _nonce, bytes _sig) public {
        require(_to != address(this));

        // Check if signature is valid and get signer's address
        address signer = signerOfSimpleCheque(_token, _to, _amount, _data, _nonce, _sig);
        require(signer != address(0));

        // Mark this cheque as used
        require (!usedNonces[signer][_nonce]);
        usedNonces[signer][_nonce] = true;

        // Send tokens
        ERC777Token token = ERC777Token(_token);
        token.operatorSend(signer, _to, _amount, _data, "");
    }


    /* Send by Aggreement */

    function signerOfAgreementCheque(bytes _agreementId, uint256 _amount, uint256 _fee, bytes _sig) private pure returns (address) {
        return keccak256(abi.encodePacked(_agreementId, _amount, _fee)).toEthSignedMessageHash().recover(_sig);
    }

    function createAgreement(bytes _id, address _token, address _payer, address _beneficiary, bytes _data) public {
        require(_beneficiary != address(0));
        require(_payer != address(0));
        //require(ERC777Token(_token));
        require(agreements[_id].beneficiary == address(0));
        agreements[_id] = Agreement({
            totalPaid: 0,
            token: _token,
            payer: _payer,
            beneficiary: _beneficiary,
            data: _data
        });
    } 

    function sendByAgreement(bytes _agreementId, uint256 _amount, uint256 _fee, bytes _sig) public returns (bool) {
        // Check if agreement exists
        Agreement storage agreement = agreements[_agreementId];
        require(agreement.beneficiary != address(0));

        // Check if signature is valid, remember last running sum
        address signer = signerOfAgreementCheque(_agreementId, _amount, _fee, _sig);
        require(signer == agreement.payer);

        // Calculate amount of tokens to be send
        uint256 amount = _amount.sub(agreement.totalPaid).sub(_fee);
        require(amount > 0);

        // If signer has less tokens that asked to transfer, we can transfer as much as he has already
        // and rest tokens can be transferred via same cheque but in another tx 
        // when signer will top up his balance.
        ERC777Token token = ERC777Token(agreement.token);
        if (amount > token.balanceOf(signer)) {
            amount = token.balanceOf(signer).sub(_fee);
        }

        // Increase already paid amount
        agreement.totalPaid = agreement.totalPaid.add(amount);

        // Send tokens
        token.operatorSend(signer, agreement.beneficiary, amount, agreement.data, "");
       
        if (_fee > 0) {
            token.operatorSend(signer, msg.sender, _fee, "", "");
        }

        return true;
    }
}

contract MyToken is ERC777Token {
    using ECRecovery for bytes32;
    mapping (address => mapping (uint256 => bool)) private usedNonces;

    constructor(address _checqueOperator) public {
        // Setting checquieOperator as default operator
        //require(ChequeOperator(_checqueOperator));
        mDefaultOperators.push(_checqueOperator);
        mIsDefaultOperator[_checqueOperator] = true;
    }
}

Additioanlly

On user's (wallet) side cheque creation could look like:

const leftPad = require('left-pad')

const hexData = [
    _agreementId,
     leftPad((_amount).toString(16), 64, 0),
     leftPad((_fee).toString(16), 64, 0)
].join('')

const msg = web3.sha3(hexData, { encoding: 'hex' }).slice(2)

const signature = web3.eth.sign(accounts[0], msg).slice(2)

Later transaction could look like:

await checqueOperator.sendByAgreement(_agreementId, _amount, _fee, signature)

Use case

This kind of cheques could potentially be widely used. Example use-case:

Frequent payments use case situation where shop gives discount points for client in a form of tokens. Also client downloads special app which is not only loyalty app but also is some kind of wallet and stores private key.

Later, when client will want to use such points (e.g. could be many times per day), without depositing some amount of gwei into his 'token wallet', he will not be able to transfer tokens. Meanwhile this token wallet could sign cheque and transfer it to shop back. Then shop (or some another entity) using this cheque could transfer tokens while paying for gas by himself.

Merchant use case the merchant generates a one-time address (OTA) and associates it with an invoice/order. The buyer transfers tokens to OTA as a means of paying the invoice.

Now the merchant wants to sweep the tokens out of OTA, but OTA cannot pay for the gas since it has no ether. So the merchant needs to send yet another transaction to fund OTA with just enough gas so it can sweep the tokens out.

The cheque model solves this problem by allowing the merchant to call sendByCheque and get the tokens out of OTA while paying for the gas from his own (master) account. This is possible because the merchant already controls OTA's keys and can produce the signature for the check.

@chompomonim chompomonim changed the title Transfer by Cheque (ERC777 extension) Send by Cheque (ERC777 extension) Apr 2, 2018
@hellwolf
Copy link

hellwolf commented May 7, 2018

I find this pattern in general a very good one for inviting people to use certain blockchain application where its users don't necessarily process the ether funds hence raising the bar of adaptation.

My few comments:

  • Would you consider to use struct and internal calls to simplify the look of the function?
  • Naming consideration: cheque seems implying this pattern be used for token/coin transactions only. But to generalize it, it is for any kind of power-of-attorney scenario.

Maybe the contract could be generalize into a PowerOfAttorny contract?

@meronym
Copy link

meronym commented May 10, 2018

I'm a strong advocate for this pattern as well. I find it useful for another payment-related core use case: the merchant generates a one-time address (OTA) and associates it with an invoice/order. The buyer transfers tokens to OTA as a means of paying the invoice.

Now the merchant wants to sweep the tokens out of OTA, but OTA cannot pay for the gas since it has no ether. So the merchant needs to send yet another transaction to fund OTA with just enough gas so it can sweep the tokens out.

The cheque model solves this problem by allowing the merchant to call sendByCheque and get the tokens out of OTA while paying for the gas from his own (master) account. This is possible because the merchant already controls OTA's keys and can produce the signature for the check.

I believe this use case will prove to be important for any token that is geared towards payments/means-of-exchange. It makes life easier for the services that need to accept payments denominated in that token, as they won't need to go through the extra-steps of sending additional transactions only for funding the one-time accounts with the gas that is later required to sweep the tokens out of those accounts.

@chompomonim how should we move forward for bringing this proposal on the table for the #777 standard?

@chompomonim
Copy link
Author

chompomonim commented May 16, 2018

@meronym this is very interesting and valid use case! I like it very much.

I'm now thinking to start some kind of repo for motivations use-cases, example code and so on around sendByCheque pattern. I'm not sure however if it should be part of #777 or just extension for it for people who would like to get all the added benefits.

Do you think we should start promoting this pattern to become part of ERC777?

@chompomonim chompomonim changed the title Send by Cheque (ERC777 extension) Send by Cheque (ERC777 extension) - transfer without paying for gas May 16, 2018
@syakunin
Copy link

syakunin commented May 16, 2018

Two issues with the code

  1. There is no callRecipient function call according to ERC777 when tokens are transferred between accounts. Argument _to can be a contract address. Consider using doSend private function instead of manually updating balances.
  2. In Solidity the hash for signature is calculated in the following way
    bytes32 hash = keccak256(prefix, keccak256(_to, _value, _data, _nonce));
    however in JavaScript message is signed like this
const hexData = [
     _to.slice(2),
     _data,
     leftPad((_value).toString(16), 64, 0),
     leftPad((_nonce).toString(16), 64, 0)
].join('')

order of parameters is wrong.

@syakunin
Copy link

Let's imagine a situation where an issuer signs a series of cheques for one person (for example 100 cheques). When a person wants to withdraw all his collected cheques, he will have to send each cheque to the blockchain and pay for each transaction. This consumes a lot of gas. What if he could withdraw all his collected cheques with one tx? A little change in the code can do that.

mapping(address => mapping(address => uint256)) public paidChequeSum;
function sendByCheque(address _to, uint256 _value, bytes _data, uint8 v, bytes32 r, bytes32 s) public returns (bool) {
     require(_to != address(this));

     // Check if signature is valid, remember last running sum
     bytes memory prefix = "\x19Ethereum Signed Message:\n32";
     bytes32 hash = keccak256(prefix, keccak256(_to, _value, _data));

     address signer = ecrecover(hash, v, r, s);
     require(signer != address(0));

     uint256 amount = _value.sub(paidChequeSum[signer][_to]);
     require(amount > 0);

     if (amount > balances[signer]) {
         amount = balances[signer];
     }

    // Increase already paid amount
    paidChequeSum[signer][_to] = paidChequeSum[signer][_to].add(amount);

    doSend(signer, _to, amount, _data, msg.sender, "", false);

    return true;
}
  • First thing to notice is the _value parameter. It is a running sum of sent tokens to a certain address. For example issuer issues a series of cheques with 2 tokens in each cheque. The first cheque will have _value=2, the second - _value=4, third - _value=6, etc.
  • Second, _nonce argument disappears, since _value is always increasing it plays the _nonce role.
  • Third, in current implementation, if the cheque issuer does not have enough funds on his balance the tx is rolled back. In proposed implementation a user will receive all remaining tokens on issuer balance and later a user can retry to withdraw left tokens using the same cheque.
  • User is not forced to store all cheques to withdraw them, he can store only the latest one and withdraw all tokens with the latest cheque in one tx (if issuer have enough balance).

Of course there are some drawbacks:

  • Issuer needs to track running sum for each user offchain, because issuer cannot get the latest issued cheque sum onchain (the cheque may not be withdrawn at the moment)
  • If the cheque series for a certain address is too large it can hit uint256 overflow

For both drawbacks the solution may be "Use the other _to address!". When issuer is in doubt of a running sum, he may ask a user for a clean address. For second drawback the same rule applies. When a cheque series is close to uint256 overflow, issuer may ask user for a clean address.

@syakunin
Copy link

One more issue with the code is that we need to pass the signer address too.
function sendByCheque(address _from, address _to, uint256 _value, bytes _data, uint8 v, bytes32 r, bytes32 s) public returns (bool);
and then, after ecrecover compare it with recovered address
require(signer == _from);

@ptrwtts
Copy link

ptrwtts commented May 17, 2018

This seems similar to #865, except it extends ERC777 rather than ERC20. An important feature in #865 is the ability to offer a fee (in tokens), to compensate the operator who submits the transaction. This fee could also be 0, but having the capability increases the applicable use-cases (e.g. an application wants to help it's users make token transfers, but not perpetually pay the cost of transactions).

@chompomonim
Copy link
Author

chompomonim commented Jun 4, 2018

@syakunin thanks for your comments. Trying to address all of them.

There is no callRecipient function call according to ERC777 when tokens are transferred between accounts. Argument _to can be a contract address. Consider using doSend private function instead of manually updating balances.

Good point. Fixed.

... order of parameters is wrong.
Fixed, thanks.

Let's imagine a situation where an issuer signs a series of cheques for one person (for example 100 cheques). When a person wants to withdraw all his collected cheques, he will have to send each cheque to the blockchain and pay for each transaction. This consumes a lot of gas. What if he could withdraw all his collected cheques with one tx? A little change in the code can do that.

Good point. Having possibility to skip some cheques in terms of saving gas (while taking some risks as well) is quite good idea. I was almost ready to accept your suggestion but I came up with even better, more in spirit of ERC777, idea --- authorizeOperatorByCheque. Will describe it in next comment.

One more issue with the code is that we need to pass the signer address too. function sendByCheque(address _from, address _to, uint256 _value, bytes _data, uint8 v, bytes32 r, bytes32 s) public returns (bool); and then, after ecrecover compare it with recovered address require(signer == _from);

Interesting... You wanna say, that ecrecover can recover into not proper signer and that could be used as an attack?

@chompomonim
Copy link
Author

This seems similar to #865, except it extends ERC777 rather than ERC20. An important feature in #865 is the ability to offer a fee (in tokens), to compensate the operator who submits the transaction. This fee could also be 0, but having the capability increases the applicable use-cases (e.g. an application wants to help it's users make token transfers, but not perpetually pay the cost of transactions).

@ptrwtts thanks for pointing into #865. Idea of offering a fee (to motivate oracles) sounds very interesting. I also wanted to accept this proposal, but it pushed me to rethink of how it should work to be in spirit of ERC777 and not ERC20. In next comment I'm going to add update of this proposal.

@chompomonim
Copy link
Author

chompomonim commented Jun 4, 2018

This is update for original proposal and if nobody will find any critical issues with it, I'm going switch original #965 description into this one.


Preamble

EIP: 965
Title: Authorize Operator by Cheque (ERC777 extension) - possibility to transfer without paying for gas
Author: Jaro Šatkevič @chompomonim, Anatoly Ressin @artazor
Type: Standard Track
Category: ERC
Status: 
Created: 2018-06-05
Requires: 777

Problem

The problem of tokens spending is that token owner have to have ETH in his account to pay for gas. So it's impossible to have pure token account. Even in ERC777 (#777) where you can have operator which can manage your tokens (and paying for gas), there still is same problem of lack of gas to initiate authorizeOperator call.

Solution

Add possibility to authorize operator by cheque. User could sign permission and send to operator via any wanted channels. Operator would send tx with signed permission into blockchain by himself so user would not need to pay for gas.

Possible function's implementation:

contract MyToken is ERC777 {
    using ECRecovery for bytes32;
    mapping (address => mapping (uint256 => bool)) private usedNonces;

    function authorizeOperatorByCheque(address _operator, uint256 _nonce, bytes _sig) public returns (bool) {
        require(_operator != address(this));

        // Getting signer address
        address signer = keccak256(_operator, _nonce).toEthSignedMessageHash().recover(_sig);
        require (signer != address(0));

        // Setting nonce to protect against repeating authorization in future
        require (!usedNonces[signer][_nonce]);
        usedNonces[signer][_nonce] = true;

        // Authorizing operator
        require(_operator != signer);
        isOperatorFor[_operator][signer] = true;
        emit AuthorizedOperator(_operator, signer);

        return true;
    }

On user's (wallet) side cheque creation could look like:

const leftPad = require('left-pad')

const hexData = [
     _operator.slice(2),
     leftPad((_nonce).toString(16), 64, 0)
].join('')

const msg = web3.sha3(hexData, { encoding: 'hex' }).slice(2)

const signature = web3.eth.sign(accounts[0], msg).slice(2)

const r = '0x' + signature.slice(0,64);
const s = '0x' + signature.slice(64, 128);
const v = Number.parseInt(signature.slice(128, 130), 16) + 27;

Later transaction could look like:

await token.sendByCheque(_operator, _nonce, v, r, s)

Operator as smart contract which is accepting cheques

If user needs more control and don't want allow operator dispose of all his balance, there could be smart contract which do send tokens as users operator but only by accepting cheques.

Such smart contract could look like this:

contract ChequeBouncer {
      using ECRecovery for bytes32;

      function signerOfCheque(address _to, uint256 _amount, uint256 _fee, bytes _data) private returns (address) {
          return keccak256(_to, _amount, _fee, _data).toEthSignedMessageHash().recover(_sig);
      }
}

contract ChequeOperator is ChequeBouncer {

    ERC777Token public token; 
    mapping(address => mapping(address => uint256)) public paidChequeSum;

    constructor(address _token) public {
        token = ERC777Token(_token);
    }

    function sendByCheque(address _to, uint256 _amount, uint256 _fee, bytes _data, bytes _sig) public returns (bool) {
         require(_to != address(this));

         // Check if signature is valid, remember last running sum
         address signer = signerOfCheque(_to, _amount, _fee, _data, _sig);
         require(signer != address(0));

         uint256 amount = _amount.sub(paidChequeSum[signer][_to]).sub(_fee);
         require(amount > 0);

         if (amount > token.balances[signer]) {
             amount = token.balances[signer].sub(_fee);
         }

        // Increase already paid amount
        paidChequeSum[signer][_to] = paidChequeSum[signer][_to].add(amount);

        // Send tokens
        token.operatorSend(signer, _to, amount, _data, "");
        token.operatorSend(signer, msg.sender, _fee, "", "");

        return true;
    }
}

Use case

I think this kind of cheques could potentially be widely used. Example use-case:

Imagine situation where shop gives discount points for client in a form of tokens. Also client downloads special app which is not only loyalty app but also is some kind of wallet and stores private key.

Later, when client will want to use such points, without depositing some amount of gwei into his 'token wallet', he will not be able to transfer tokens. Meanwhile this token wallet could sign cheque and transfer it to shop back. Then shop (or some another entity) using this cheque could transfer tokens while paying for gas by himself.

@shrugs
Copy link

shrugs commented Jun 4, 2018

This functionality could/should be implemented as a SignatureBouncer (https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/access/SignatureBouncer.sol) which is designed to solve exactly these sort of "gassless" operations.

This sort of "verify function call parameters" feature is pending as part of OpenZeppelin/openzeppelin-contracts#973

@meronym
Copy link

meronym commented Jun 5, 2018

@shrugs If I understand correctly, with the bouncer model everyone can send txs, but a selected few bouncers can sign the allowance. In the model proposed here, everyone can sign a cheque (to allow spending on their behalf), and anybody can redeem (in practice there are use cases when only a few addresses will redeem - see my example above with one-time addresses - but that's not explicitly constrained by the model).

The onlyOwner thus seems superfluous for the cheque model, as there's no need for a central authority to create a whitelist. Everyone can sign checks to allow anybody else to redeem them.

@shrugs
Copy link

shrugs commented Jun 6, 2018

Sure! I should have elaborated more, sorry. The model would definitely be different than the "off chain whitelist" approach that Signature Bouncer takes, but the concept of "sign a permission for taking a future action with certain arguments" fits in well. Perhaps a ChequeBouncer that verifiers amounts and signers?

It would be something like

contract ChequeBouncer {
  using ECRecovery for bytes32;

  // only verify signer, this contract, and amount. this hash where you'd add a nonce
  function signerOfCheque(uint256 _amount, bytes _sig)
    private
    returns (address)
  {
    return keccak256(
        address(this),
        _amount,
      )
      .toEthSignedMessageHash()
      .recover(_sig);
  }
}

contract MyToken is ERC777, ChequeBouncer {
  // allows anyone with signature to collect _amount tokens from signer and send them to _to
  function sendByCheque(address _to, uint256 _amount, bytes _sig)
    public
  {
    address signer = signerOfCheque(_amount, _sig);
    require(signer != address(0));
    doSend(signer, _to, _amount);
  }
}

The "gassless tx with Operator" pattern is also a good use-case for Signature Bouncer. Imagine a Proxy contract that allows anyone to call it with a meta transaction, but only if that metatransaction is signed by the owner (bouncer) of that proxy.


(although if you'd like to guarantee the checks by making the committed funds unspendable until cashed, issuing that commitment to the cheque on-chain is necessary)

@meronym
Copy link

meronym commented Jun 6, 2018

I'd include the _to address in the hash as well, as I feel that a white cheque's requirements for secure channel transmission and storage (so it cannot be eavesdropped, hacked or spent in a different way than originally intended) outbalances the flexibility gains.

I'm not sure I understand the meta-transaction example, can you get into a bit more details on how it'd work?

@chompomonim
Copy link
Author

@shrugs thanks for mentioning ChequeBouncer. I'm totally ok to have implementation using this approach and already fixed my proposal (in comment) above.

Anyway now it's important to set interface for authorizeOperatorByCheque or/and for sendByCheque and then play with implementations.

@meronym, @syakunin, @ptrwtts have you looked on authorizeOperatorByCheque idea? Does it sounds better than pure sendByCheque proposed initially?

@meronym
Copy link

meronym commented Jun 7, 2018

@chompomonim In general I like the authorizeOperatorByCheque approach more because it allows for more flexibility.

Now the question is whether we should include a sendByCheque method as well, to which I'd vote yes, justified by my example above re sweeping the tokens from one-time addresses. If the authorizeOperatorByCheque is the only method available, the workflow would still require two different transactions (one for authorization and one from the operator), which seems a bit overkill.

@chompomonim
Copy link
Author

Now the question is whether we should include a sendByCheque method as well, to which I'd vote yes, justified by my example above re sweeping the tokens from one-time addresses. If the authorizeOperatorByCheque is the only method available, the workflow would still require two different transactions (one for authorization and one from the operator), which seems a bit overkill.

Good point. In your case it's better to have sendByCheque as part of token because it's one time operation for each address.

From other side if it's ERC777 send and wallet which supports it, you could not generate a lot of addresses, but ask people to set some data while transferring tokens. Then you'll not need to generate a lot of separate addresses. Unfortunately we're not in that world yet...

@meronym
Copy link

meronym commented Jun 7, 2018

From other side if it's ERC777 send and wallet which supports it, you could not generate a lot of addresses, but ask people to set some data while transferring tokens. Then you'll not need to generate a lot of separate addresses. Unfortunately we're not in that world yet...

Agreed. The point of one-time addresses is legacy support for clients (i.e. wallets or custodian exchange accounts) that don't allow for customization of the token transfer data. I expect that client/wallet support for ERC777 will take quite a bit to mature, and an intermediate solution (such as sendByCheque can fill in the adoption time gap and enable the merchants accept payments from legacy clients while providing a mechanism for associating the payments with an invoice.

@chompomonim
Copy link
Author

chompomonim commented Jun 24, 2018

After some considerations I see that there is one more fix needed for ChecqueOperator.

If we're using paidChequeSum which accumulates sum we should not use data, because any of cheques could be skipped. So data can't be parameter of sendByCheque because some datas could be skipped.

To solve this problem we could add some kind of agreements functionality:

contract ChequeOperator is ChequeBouncer {

    ERC777Token public token; 
    struct Agreement {
        uint256 totalPaid;
        address payer;
        address beneficiary;
        bytes data;
    }
    mapping(bytes32 => Agreement) public agreements;

    constructor(address _token) public {
        token = ERC777Token(_token);
    }

    function createAgreement(bytes32 _id, address _payer, address _beneficiary, bytes _data) {
        require(_beneficiary != address(0));
        require(_payer != address(0));
        require(agreements[_id].beneficiary == address(0));
        agreements[_id] = Agreement({
            totalPaid: 0,
            payer: _payer,
            beneficiary: _beneficiary,
            data: _data
        });
    } 

    function sendByCheque(bytes32 _agreementId, uint256 _amount, uint256 _fee, bytes _sig) public returns (bool) {
         // Check if agreement exists
         Agreement storage agreement = agreements[_agreementId];
         require(agreement.beneficiary != address(0));

         // Check if signature is valid, remember last running sum
         address signer = signerOfCheque(_agreementId, _amount, _fee, _sig);
         require(signer == agreement.payer);

         // Calculate amount of tokens to be send
         uint256 amount = _amount.sub(agreement.totalPaid).sub(_fee);
         require(amount > 0);

         // If signer has less tokens that asked to transfer, we can transfer as much as he has already
         // and rest tokens can be transferred via same cheque but in another tx 
         // when signer will top up his balance.
         if (amount > token.balances[signer]) {
             amount = token.balances[signer].sub(_fee);
         }

        // Increase already paid amount
        agreement.totalPaid = agreement.totalPaid.add(amount);

        // Send tokens
        token.operatorSend(signer, agreement.beneficiary, amount, agreement.data, "");
       
        if (_fee > 0) {
            token.operatorSend(signer, msg.sender, _fee, "", "");
        }

        return true;
    }
}

I like this idea but it's good only for frequent token sends among two sides for similar purpose (agreement). Also it could be implemented in many way. Token standard don't need to have any knowledge about that. It's just my suggestion.

It's not very good for @meronym usecase. So let's have in ERC965 two functions:

    function authorizeOperatorBySignature(address _operator, uint256 _nonce, bytes _sig) public;
    function sendBySignature(address _to, uint256 _amount, bytes _data, uint256 _nonce, bytes _sig) public;

I also propose to change cheque into signature, because cheque is something associated with money and not with paper which setting permissions.

There are no fee in sendBySignature because it should be part of ChequeOperator smart contract and is not needed for one shot sends.

@mg6maciej
Copy link
Contributor

mg6maciej commented Aug 5, 2018

Could you fix or delete code in #965 (comment) and #964 to remove security issue? Someone might think it's a good code and use it.
Contract address (this) should be hashed with all the other data to avoid replay attacks.

@chompomonim chompomonim changed the title Send by Cheque (ERC777 extension) - transfer without paying for gas Send by Signature (ERC777 extension) - transfer without paying for gas Aug 5, 2018
@chompomonim
Copy link
Author

@mg6maciej thanks for pinging me. After ERC777 got default operators I was willing to fix this proposal to use default operator for sendByChecque. Today I did it.

I deleted description from outdated #964 and updated main description of this issue.

Does it solves your security concerns?

@meronym
Copy link

meronym commented Aug 22, 2018

Well done @chompomonim ! There are a few typos in the method definitions and subsequent calls: signerOfAggrementCheque, sendByAgrrement.

@chompomonim
Copy link
Author

@meronym thanks for pointing typos. I just fixed them.

@chompomonim
Copy link
Author

After some thoughts and discussion around potential infrastructure build around this standard (some kind of nodes which would take cheques, get fee in tokens and persist them into blockchain), I see that more changes are needed for this proposal.

  1. Possibility to create createAgreement via checque by paying fee in tokens for agreement creation (agreements are needed for batched transactions).
  2. Possibility to create agreement where for persisting cheque would pay third party (subsidize using some dApp).
  3. Having possibility to put tokens into escrow (e.g. when creating agreement) so service provider could provide services without waiting for tx being persisted into blockchain (e.g. in case when provider will accept cheques by himself, batch them and later persist into blockchain).

@chompomonim
Copy link
Author

Another idea is that ERC965 could work well with ERC20 kind of tokens if they would have default operators there. Does anyone know if there is already any ERC for #777 type of operators but for ERC20?

@MrChico
Copy link
Member

MrChico commented Oct 2, 2018

I think this should be augmented to be compatible with https://eips.ethereum.org/EIPS/eip-712

@oberstet
Copy link
Contributor

oberstet commented Apr 14, 2019

As far as I can see, there are currently 2 proposals for tokens that allow gasless token transfers:

Are there more proposals?
Any new directions or recommendations rgd this feature in general?

@wighawag
Copy link
Contributor

There is also #1776

@lastmjs
Copy link

lastmjs commented Apr 16, 2019

Hey everyone, I'm new to this, but this feature is very important to a use case for my project. What is involved in getting this proposal or similar proposals for gas delegation moving and implemented, and how long should I expect this to take? I'm most likely willing to put significant effort into helping this along come summer, I just need some guidance.

@chompomonim
Copy link
Author

The main problem is that there is needed additional standard for wallets and services which would agree on accepting tokens as fee and send transaction into Ethereum networks with your signatures.

If you need that only for your own token, then you can use such techniques already today, you don't need to wait until any finalisations of one or another standard. But if you'd like to have such features in major wallets, then it's really not going to happen any time soon.

@oberstet
Copy link
Contributor

@chompomonim ok, I see. can be made work (generically) in metamask (based app/sites), but not "for any wallet". fwiw, this gasless stuff is important IMO for UX though - practically (non pro users .. your mom;) anyways, thanks for hints and opinion!

@jessedionoppchain
Copy link

Hi everyone, just to confirm, are we agree that all this solution require than a third part actually "create" the transaction?

I mean as an end user with only tokens on my account can I, with the signature mecanism, make a transaction published by a smart contract (which hold some ether to pay the gas), without an action from a particular owner?

@csajedi
Copy link

csajedi commented Apr 10, 2021

Not sure if this is entirely appropriate but I used this design for the basis of another token on the Zilliqa Blockchain. Just wanted to give some credit and say thanks for the background work you all did here.
https://cameronsajedi.medium.com/zrc3s-grand-rewrite-22558797ea0

@github-actions
Copy link

There has been no activity on this issue for two months. It will be closed in a week if no further activity occurs. If you would like to move this EIP forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review.

@github-actions github-actions bot added the stale label Dec 18, 2021
@github-actions
Copy link

github-actions bot commented Jan 1, 2022

This issue was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback or request a review in a comment.

@github-actions github-actions bot closed this as completed Jan 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

15 participants