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.
Preamble
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
authorizeOperatorcall.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:
Additioanlly
On user's (wallet) side cheque creation could look like:
Later transaction could look like:
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.