--- eip: 1175 title: Wallet & shop standard for all tokens (erc20) author: Jet Lim (@Nitro888) discussions-to: https://github.com/ethereum/EIPs/issues/1182 status: Draft type: Standards Track category: ERC created: 2018-06-21 requires: 20 --- # All tokens go to heaven ## Simple Summary Make wallets and shops created from certified contracts make erc20 tokens easy to use for commerce. ![wallet](https://user-images.githubusercontent.com/11692220/41762799-ee17c480-7636-11e8-9930-681be2c59b56.png) ## 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](https://user-images.githubusercontent.com/11692220/41841025-2ed6e024-78a2-11e8-9faf-2b43aeaa2303.png) ## 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](https://ropsten.etherscan.io/address/0x393dd70ce2ae7b30501aec94727968c517f90d52) WalletCenter contract address is [0x1fe0862a4a8287d6c23904d61f02507b5044ea31](https://ropsten.etherscan.io/address/0x1fe0862a4a8287d6c23904d61f02507b5044ea31) WalletCenter create shop contract address is [0x59117730D02Ca3796121b7975796d479A5Fe54B0](https://ropsten.etherscan.io/address/0x59117730D02Ca3796121b7975796d479A5Fe54B0) WalletCenter create wallet contract address is [0x39da7111844df424e1d0a0226183533dd07bc5c6](https://ropsten.etherscan.io/address/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 Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).