mirror of https://github.com/status-im/EIPs.git
534 lines
18 KiB
Markdown
534 lines
18 KiB
Markdown
---
|
|
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/).
|