AlertSourceDiscuss
Skip to content

ERC-1175: Wallet & shop standard for all tokens (erc20)

🚧 StagnantERC

Stagnant

This EIP has had no recent activity for at least 6 months, and has automatically been marked as stagnant. This EIP should not be used in production.

If you are interested in helping move this EIP to final, create a PR to move this EIP back to Draft and add yourself as an author, and an EIP editor will help guide you through the process. Thank you!

AuthorsJet Lim (@Nitro888)
Created2018-06-21

All tokens go to heaven ​

Simple Summary ​

Make wallets and shops created from certified contracts make erc20 tokens easy to use for commerce.

wallet

Abstract ​

The mutual trust between the wallet and the shop created by the authenticated contract allows you to pay for and purchase items at a simple process.

Motivation ​

New standards with improvements have been released, but the majority of tokens currently being developed are erc20 tokens. So I felt I needed a proposal to use old tokens in commerce. To use various erc20 tokens for trading, you need a custom contract. However, a single wallet with a variety of tokens, and a mutually trusted store, can make transactions that are simple and efficient. The erc20 token is traded through two calls, approve (address _spender, uint256 _value) and transferFrom (address _from, address _to, uint256 _value), but when using the wallet contract, paySafe (address _shop, uint256 _item)will be traded only in one call. And if you only reuse the store interface, you can also trade using payUnsafe (address _shop, uint256 _item).

Specification ​

workflow

WalletCenter ​

Methods ​

createWallet ​

Create wallet contract and add to list. Returns the address of new wallet.

js
function createWallet() public returns (address _wallet)

isWallet ​

Returns true or false value for test this address is a created by createWallet.

js
function isWallet(address _wallet) public constant returns (bool)

createShop ​

Create Shop contract and add to list. Returns the address of new Shop with erc20 token address.

js
function createShop(address _erc20) public returns (address _shop)

isShop ​

Returns true or false value for test this address is a created by createWallet.

js
function isShop(address _shop) public constant returns (bool)

Events ​

Wallet ​

Search for my wallet.

js
event Wallet(address indexed _owner, address indexed _wallet)

Shop ​

Search for my shop.

js
event Shop(address indexed _owner, address indexed _shop, address indexed _erc20)

Wallet ​

Wallet must be created by wallet center.

Methods ​

balanceOf ​

Returns the account balance of Wallet.

js
function balanceOf(address _erc20) public constant returns (uint256 balance)

withdrawal ​

withdrawal _value amount of _erc20 token to _owner.

js
function withdrawal(address _erc20, uint256 _value) onlyOwner public returns (bool success)

paySafe ​

Pay for safe shop (created by contract) item with item index _item.

js
function paySafe(address _shop, uint256 _item) onlyOwner onlyShop(_shop) public payable returns (bool success)

payUnsafe ​

Pay for unsafe shop (did not created by contract) item with item index _item.

js
function payUnsafe(address _shop, uint256 _item) onlyOwner public payable returns (bool success)

payCancel ​

Cancel pay and refund. (only weekly model)

js
function payCancel(address _shop, uint256 _item) onlyOwner public returns (bool success)

refund ​

Refund from shop with item index _item.

js
function refund(uint256 _item, uint256 _value) public payable returns (bool success)

Events ​

Pay ​

js
event Pay(address indexed _shop, uint256 indexed _item, uint256 indexed _value)

Refund ​

js
event Refund(address indexed _shop, uint256 indexed _item, uint256 indexed _value)

Shop ​

Shop is created by wallet center or not. but Shop that created by wallet center is called safe shop.

Methods ​

balanceOf ​

Returns the account balance of Shop.

js
function balanceOf(address _erc20) public constant returns (uint256 balance)

withdrawal ​

withdrawal _value amount of _erc20 token to _owner.

js
function withdrawal(address _erc20, uint256 _value) onlyOwner public returns (bool success)

pay ​

Pay from buyer with item index _item.

js
function pay(uint256 _item) onlyWallet(msg.sender) public payable returns (bool success)

refund ​

refund token to _to.

js
function refund(address _buyer, uint256 _item, uint256 _value) onlyWallet(_buyer) onlyOwner public payable returns (bool success)

resister ​

Listing item for sell.

js
function resister(uint8 _category, uint256 _price, uint256 _stock) onlyOwner public returns (uint256 _itemId)

update ​

Update item state for sell. (change item _price or add item _stock)

js
function update(uint256 _item, uint256 _price, uint256 _stock) onlyOwner public

price ​

Get token address and price from buyer with item index _item.

js
function price(uint256 _item) public constant returns (address _erc20, uint256 _value)

canBuy ​

_who can Buy _item.

js
function canBuy(address _who, uint256 _item) public constant returns (bool _canBuy)

isBuyer ​

_who is buyer of _item.

js
function isBuyer(address _who, uint256 _item) public constant returns (bool _buyer)

info ​

Set shop information bytes.

js
function info(bytes _msgPack)

upVote ​

Up vote for this shop.

js
function upVote()

dnVote ​

Down vote for this shop.

js
function dnVote()

about ​

Get shop token, up vote and down vote.

js
function about() view returns (address _erc20, uint256 _up, uint256 _down)

infoItem ​

Set item information bytes.

js
function infoItem(uint256 _item, bytes _msgPack)

upVoteItem ​

Up vote for this item.

js
function upVoteItem(uint256 _item)

dnVoteItem ​

Down vote for this item.

js
function dnVoteItem(uint256 _item)

aboutItem ​

Get Item price, up vote and down vote.

js
function aboutItem(uint256 _item) view returns (uint256 _price, uint256 _up, uint256 _down)

Events ​

Pay ​

js
event Pay(address indexed _buyer, uint256 indexed _item, uint256 indexed _value)

Refund ​

js
event Refund(address indexed _to, uint256 indexed _item, uint256 indexed _value)

Item ​

js
event Item(uint256 indexed _item, uint256 _price)

Info ​

js
event Info(bytes _msgPack)

InfoItem ​

js
event InfoItem(uint256 indexed _item, bytes _msgPack)

Implementation ​

Sample token contract address is 0x393dd70ce2ae7b30501aec94727968c517f90d52

WalletCenter contract address is 0x1fe0862a4a8287d6c23904d61f02507b5044ea31

WalletCenter create shop contract address is 0x59117730D02Ca3796121b7975796d479A5Fe54B0

WalletCenter create wallet contract address is 0x39da7111844df424e1d0a0226183533dd07bc5c6

Appendix ​

js
pragma solidity ^0.4.24;

contract ERC20Interface {
    function totalSupply() public constant returns (uint);
    function balanceOf(address tokenOwner) public constant returns (uint balance);
    function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
    function transfer(address to, uint tokens) public returns (bool success);
    function approve(address spender, uint tokens) public returns (bool success);
    function transferFrom(address from, address to, uint tokens) public returns (bool success);

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

contract SafeMath {
    function safeAdd(uint a, uint b) public pure returns (uint c) {
        c = a + b;
        require(c >= a);
    }
    function safeSub(uint a, uint b) public pure returns (uint c) {
        require(b <= a);
        c = a - b;
    }
    function safeMul(uint a, uint b) public pure returns (uint c) {
        c = a * b;
        require(a == 0 || c / a == b);
    }
    function safeDiv(uint a, uint b) public pure returns (uint c) {
        require(b > 0);
        c = a / b;
    }
}

contract _Base {
    address internal owner;
    address internal walletCenter;
    
    modifier onlyOwner {
        require(owner == msg.sender);
        _;
    }
    modifier onlyWallet(address _addr) {
        require(WalletCenter(walletCenter).isWallet(_addr));
        _;
    }
    modifier onlyShop(address _addr) {
        require(WalletCenter(walletCenter).isShop(_addr));
        _;
    }

    function balanceOf(address _erc20) public constant returns (uint256 balance) {
        if(_erc20==address(0))
            return address(this).balance;
        return ERC20Interface(_erc20).balanceOf(this);
    }

    function transfer(address _to, address _erc20, uint256 _value) internal returns (bool success) {
        require((_erc20==address(0)?address(this).balance:ERC20Interface(_erc20).balanceOf(this))>=_value);
        if(_erc20==address(0))
            _to.transfer(_value);
        else
            ERC20Interface(_erc20).approve(_to,_value);
        return true;
    }
    
    function withdrawal(address _erc20, uint256 _value) public returns (bool success);
    
    event Pay(address indexed _who, uint256 indexed _item, uint256 indexed _value);
    event Refund(address indexed _who, uint256 indexed _item, uint256 indexed _value);
    event Prize(address indexed _who, uint256 indexed _item, uint256 indexed _value);
}

contract _Wallet is _Base {
    constructor(address _who) public {
        owner           = _who;
        walletCenter    = msg.sender;
    }
    
    function pay(address _shop, uint256 _item) private {
        require(_Shop(_shop).canBuy(this,_item));

        address _erc20;
        uint256 _value;
        (_erc20,_value) = _Shop(_shop).price(_item);
        
        transfer(_shop,_erc20,_value);
        _Shop(_shop).pay(_item);
        emit Pay(_shop,_item,_value);
    }
    
    function paySafe(address _shop, uint256 _item) onlyOwner onlyShop(_shop) public payable returns (bool success) {
        pay(_shop,_item);
        return true;
    }
    function payUnsafe(address _shop, uint256 _item) onlyOwner public payable returns (bool success) {
        pay(_shop,_item);
        return true;
    }
    function payCancel(address _shop, uint256 _item) onlyOwner public returns (bool success) {
        _Shop(_shop).payCancel(_item);
        return true;
    }

    function refund(address _erc20, uint256 _item, uint256 _value) public payable returns (bool success) {
        require((_erc20==address(0)?msg.value:ERC20Interface(_erc20).allowance(msg.sender,this))==_value);
        if(_erc20!=address(0))
            ERC20Interface(_erc20).transferFrom(msg.sender,this,_value);
        emit Refund(msg.sender,_item,_value);
        return true;
    }
    function prize(address _erc20, uint256 _item, uint256 _value) public payable returns (bool success) {
        require((_erc20==address(0)?msg.value:ERC20Interface(_erc20).allowance(msg.sender,this))==_value);
        if(_erc20!=address(0))
            ERC20Interface(_erc20).transferFrom(msg.sender,this,_value);
        emit Prize(msg.sender,_item,_value);
        return true;
    }
    
    function withdrawal(address _erc20, uint256 _value) onlyOwner public returns (bool success) {
        require((_erc20==address(0)?address(this).balance:ERC20Interface(_erc20).balanceOf(this))>=_value);
        if(_erc20==address(0))
            owner.transfer(_value);
        else
            ERC20Interface(_erc20).transfer(owner,_value);
        return true;
    }
}

contract _Shop is _Base, SafeMath{
    address erc20;
    constructor(address _who, address _erc20) public {
        owner           = _who;
        walletCenter    = msg.sender;
        erc20           = _erc20;
    }
    
    struct item {
        uint8                       category;   // 0 = disable, 1 = non Stock, non Expire, 2 = can Expire (after 1 week), 3 = stackable
        uint256                     price;
        uint256                     stockCount;

        mapping(address=>uint256)   customer;
    }

    uint                    index;
    mapping(uint256=>item)  items;
    
    function pay(uint256 _item) onlyWallet(msg.sender) public payable returns (bool success) {
        require(canBuy(msg.sender, _item));
        require((erc20==address(0)?msg.value:ERC20Interface(erc20).allowance(msg.sender,this))==items[_item].price);
        
        if(erc20!=address(0))
            ERC20Interface(erc20).transferFrom(msg.sender,this,items[_item].price);
        
        if(items[_item].category==1 || items[_item].category==2 && now > safeAdd(items[_item].customer[msg.sender], 1 weeks))
            items[_item].customer[msg.sender]   = now;
        else if(items[_item].category==2 && now < safeAdd(items[_item].customer[msg.sender], 1 weeks) )
            items[_item].customer[msg.sender]   = safeAdd(items[_item].customer[msg.sender], 1 weeks);
        else if(items[_item].category==3) {
            items[_item].customer[msg.sender]   = safeAdd(items[_item].customer[msg.sender],1);
            items[_item].stockCount             = safeSub(items[_item].stockCount,1);
        }

        emit Pay(msg.sender,_item,items[_item].customer[msg.sender]);
        return true;
    }
    
    function payCancel(uint256 _item) onlyWallet(msg.sender) public returns (bool success) {
        require (items[_item].category==2&&safeAdd(items[_item].customer[msg.sender],2 weeks)>now&&balanceOf(erc20)>=items[_item].price);

        items[_item].customer[msg.sender]  = safeSub(items[_item].customer[msg.sender],1 weeks);
        transfer(msg.sender, erc20, items[_item].price);
        _Wallet(msg.sender).refund(erc20,_item,items[_item].price);
        emit Refund(msg.sender,_item,items[_item].price);

        return true;
    }
    function refund(address _to, uint256 _item) onlyWallet(_to) onlyOwner public payable returns (bool success) {
        require(isBuyer(_to,_item)&&items[_item].category>0&&(items[_item].customer[_to]>0||(items[_item].category==2&&safeAdd(items[_item].customer[_to],2 weeks)>now)));
        require((erc20==address(0)?address(this).balance:ERC20Interface(erc20).balanceOf(this))>=items[_item].price);

        if(items[_item].category==1)
            items[_item].customer[_to]  = 0;
        else if(items[_item].category==2)
            items[_item].customer[_to]  = safeSub(items[_item].customer[_to],1 weeks);
        else
            items[_item].customer[_to]  = safeSub(items[_item].customer[_to],1);
            
        transfer(_to, erc20, items[_item].price);
        _Wallet(_to).refund(erc20,_item,items[_item].price);
        emit Refund(_to,_item,items[_item].price);

        return true;
    }
    
    event Item(uint256 indexed _item, uint256 _price);
    function resister(uint8 _category, uint256 _price, uint256 _stock) onlyOwner public returns (uint256 _itemId) {
        require(_category>0&&_category<4);
        require(_price>0);
        items[index]    = item(_category,_price,_stock);
        index = safeAdd(index,1);
        emit Item(index,_price);
        return safeSub(index,1);
    }
    function update(uint256 _item, uint256 _price, uint256 _stock) onlyOwner public {
        require(items[_item].category>0);
        require(_price>0);
        uint256 temp = items[_item].price;
        items[_item].price      = _price;
        items[_item].stockCount = safeAdd(items[_item].stockCount,_stock);
        
        if(temp!=items[_item].price)
            emit Item(index,items[_item].price);
    }
    
    function price(uint256 _item) public constant returns (address _erc20, uint256 _value) {
        return (erc20,items[_item].price);
    }
    
    function canBuy(address _who, uint256 _item) public constant returns (bool _canBuy) {
        return  (items[_item].category>0) &&
                !(items[_item].category==1&&items[_item].customer[_who]>0) &&
                (items[_item].stockCount>0);
    }
    
    function isBuyer(address _who, uint256 _item) public constant returns (bool _buyer) {
        return (items[_item].category==1&&items[_item].customer[_who]>0)||(items[_item].category==2&&safeAdd(items[_item].customer[_who],1 weeks)>now)||(items[_item].category==3&&items[_item].customer[_who]>0);
    }
    
    uint lastWithdrawal;
    function withdrawal(address _erc20, uint256 _value) onlyOwner public returns (bool success) {
        require(safeAdd(lastWithdrawal,1 weeks)<=now);
        require((_erc20==address(0)?address(this).balance:ERC20Interface(_erc20).balanceOf(this))>=_value);
        if(_erc20==address(0))
            owner.transfer(_value);
        else
            ERC20Interface(_erc20).transfer(owner,_value);
        lastWithdrawal = now;
        return true;
    }
}

contract WalletCenter {
    mapping(address=>bool) public     wallet;
    event Wallet(address indexed _owner, address indexed _wallet);
    function createWallet() public returns (address _wallet) {
        _wallet = new _Wallet(msg.sender);
        wallet[_wallet] = true;
        emit Wallet(msg.sender,_wallet);
        return _wallet;
    }
    function isWallet(address _wallet) public constant returns (bool) {
        return wallet[_wallet];
    }
    mapping(address=>bool) public     shop;
    event Shop(address indexed _owner, address indexed _shop, address indexed _erc20);
    function createShop(address _erc20) public returns (address _shop) {
        _shop   = new _Shop(msg.sender,_erc20);
        shop[_shop] = true;
        emit Shop(msg.sender,_shop,_erc20);
        return _shop;
    }
    function isShop(address _shop) public constant returns (bool) {
        return shop[_shop];
    }
}

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Jet Lim, "ERC-1175: Wallet & shop standard for all tokens (erc20)[DRAFT]," Ethereum Improvement Proposals, no. 1175, 2018. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1175.