使用代币替代传统积分系统

本文节选自电子书《Netkiller Blockchain 手札》

Netkiller Blockchain 手札

本文作者最近在找工作,有意向致电 13113668890

Mr. Neo Chan, 陈景峯(BG7NYT)

中国广东省深圳市龙华新区民治街道溪山美地 518131 +86 13113668890 <netkiller@msn.com>

文档始创于2018-02-10

版权 © 2018 Netkiller(Neo Chan). All rights reserved.

版权声明

转载请与作者联系,转载时请务必标明文章原始出处和作者信息及本声明。

微信订阅号 netkiller-ebook (微信扫描二维码)

QQ:13721218 请注明“读者”

QQ群:128659835 请注明“读者”

网站:http://www.netkiller.cn

33.6. 使用代币替代传统积分系统

首先我们使用代币是为了取代传统的积分机制。因为代币的“币”特性能够实现流通,交易等等,而传统的积分只能内部使用,无法流通,外接不承认加分的价值,积分无法交易,流通。

其次我们并非是为了ICO炒作,然后割韭菜,代币取代积分是刚性需求。

这里假设您已经看我我之前写的文章,知道什么是代币,并且能用钱包部署代币合约。例如现在合约已经部署到主网,已经可以使用钱包转账代币,甚至代币已经上了交易所,接下来我们要做什么呢?

接下来的工作是将代币和网站或者手机App打通,只有将代币整合到现有业务场景中代币才有意义。

33.6.1. 规划

33.6.1.1. 账号规划

我认为我们需要下面几种角色的账号

  • 代币发行账号,负责发行代币,管理代币
  • 收款账号,用户代币流通中的收款,这个账号应该由财务人员负责,相当于企业对公账号。
  • 交易所账号,用来对接交易所
  • 用户账号,普用户的账号,依赖接受代币,消费代币,交易代币。

33.6.1.2. 日志规划

日志

  1. 开户日志
  2. 绑定日志
  3. 送币日志
  4. 商城日志,代币转账日志

33.6.1.3. 监控规划

监控

监控内容

  1. 额度监控,监控账号额度变化
  2. 交易监控,监控任何一笔交易
  3. 异常监控

33.6.2. 实施步骤

如果着手一个游戏项目上链,我们需要怎么做呢?

上链步骤

  • 收集需求,收集公司的内部上链需求,听取所有部门的建议和诉求。 收集内容例如,代币发行量多少?代币小数位数,代币名称,是否会增发,是否需要冻结,代币怎样流通,怎样回收 Dapp 的 UI 设计,各种功能流程
  • 分析需求,因为需求来自各种部门,各种岗位等等,他们不一定从事需求分析工作,所以我们需求对他们的需求过滤,分析,然后给出初步的PRD文档(产品需求文档)
  • 根据收集的需求设计合约和Dapp 根据需求设计Dapp 系统架构设计,软件架构设计,技术选型;需要考虑扩展性,灵活性,并发设计,数据安全,部署,后期监控,各种埋点(统计、监控)
  • 准备环境,我们需要三个环境,开发,测试,生产(运营)。
  • 项目启动 运维部门准备环境,开始建设监控系统 开发部门开发合约和Dapp 测试部门准备测试用例,测试环境
  • 测试 Alpha 阶段,将合约部署到测试环境,测试合约的每个函数的工作逻辑,确保无误。因为合约一旦部署将不能更改,只能废弃使用新的合约,所以这个测试步骤必须重视。 Beta 阶段,将测试合约部署到测试网,例如 Ropsten ,可以邀请公司内部测试
  • 部署生产环境 部署合约,将合约部署到主网 Dapp 部署到生产环境。
  • 验收测试,在生产环境做最后验收测试
  • 代币上交易所

33.6.3. ERC20 代币合约

合约部署这里加就不介绍了,可以参考《Netkiller Blockchain 手札》

合约地址:https://raw.githubusercontent.com/ibook/TokenERC20/master/contracts/TokenERC20.sol

这个合约提供增发,冻结,所有权转移等等功能。

pragma solidity ^0.4.21;

interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }

contract TokenERC20 {
    address public owner;
    // Public variables of the token
    string public name;
    string public symbol;
    uint8 public decimals = 18;
    // 18 decimals is the strongly suggested default, avoid changing it
    uint256 public totalSupply;

    // This creates an array with all balances
    mapping (address => uint256) public balanceOf;
    mapping (address => mapping (address => uint256)) public allowance;

    // This generates a public event on the blockchain that will notify clients
    event Transfer(address indexed from, address indexed to, uint256 value);

    // This notifies clients about the amount burnt
    event Burn(address indexed from, uint256 value);

    mapping (address => bool) public frozenAccount;
    event FrozenFunds(address target, bool frozen);

    /**
     * Constrctor function
     *
     * Initializes contract with initial supply tokens to the creator of the contract
     */
    function TokenERC20(
        uint256 initialSupply,
        string tokenName,
        string tokenSymbol
    ) public {
        owner = msg.sender;
        
        totalSupply = initialSupply * 10 ** uint256(decimals);  // Update total supply with the decimal amount
        balanceOf[msg.sender] = totalSupply;                // Give the creator all initial tokens
        name = tokenName;                                   // Set the name for display purposes
        symbol = tokenSymbol;                               // Set the symbol for display purposes
    }

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    /**
     * Internal transfer, only can be called by this contract
     */
    function _transfer(address _from, address _to, uint _value) internal {
        // Prevent transfer to 0x0 address. Use burn() instead
        require(_to != 0x0);
        // Check if the sender has enough
        require(balanceOf[_from] >= _value);
        // Check for overflows
        require(balanceOf[_to] + _value > balanceOf[_to]);
        // Save this for an assertion in the future
        uint previousBalances = balanceOf[_from] + balanceOf[_to];
        // Subtract from the sender
        balanceOf[_from] -= _value;
        // Add the same to the recipient
        balanceOf[_to] += _value;
        Transfer(_from, _to, _value);
        // Asserts are used to use static analysis to find bugs in your code. They should never fail
        assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
    }
    
    function transfer(address _to, uint256 _value) public {
        require(!frozenAccount[msg.sender]);
        _transfer(msg.sender, _to, _value);
    }

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        require(!frozenAccount[msg.sender]);
        require(_value <= allowance[_from][msg.sender]);     // Check allowance
        allowance[_from][msg.sender] -= _value;
        _transfer(_from, _to, _value);
        return true;
    }

    function approve(address _spender, uint256 _value) public
        returns (bool success) {
        allowance[msg.sender][_spender] = _value;
        return true;
    }

    function approveAndCall(address _spender, uint256 _value, bytes _extraData)
        public
        returns (bool success) {
        tokenRecipient spender = tokenRecipient(_spender);
        if (approve(_spender, _value)) {
            spender.receiveApproval(msg.sender, _value, this, _extraData);
            return true;
        }
    }

    function burn(uint256 _value) onlyOwner public returns (bool success) {
        require(balanceOf[msg.sender] >= _value);   // Check if the sender has enough
        balanceOf[msg.sender] -= _value;            // Subtract from the sender
        totalSupply -= _value;                      // Updates totalSupply
        Burn(msg.sender, _value);
        return true;
    }


    function burnFrom(address _from, uint256 _value) onlyOwner public returns (bool success) {
        require(balanceOf[_from] >= _value);                // Check if the targeted balance is enough
        require(_value <= allowance[_from][msg.sender]);    // Check allowance
        balanceOf[_from] -= _value;                         // Subtract from the targeted balance
        allowance[_from][msg.sender] -= _value;             // Subtract from the sender's allowance
        totalSupply -= _value;                              // Update totalSupply
        Burn(_from, _value);
        return true;
    }


  function transfer(address _to, uint256 _value, bytes _data) public returns (bool) {
    require(_to != address(this));
    transfer(_to, _value);
    require(_to.call(_data));
    return true;
  }

  function transferFrom(address _from, address _to, uint256 _value, bytes _data) public returns (bool) {
    require(_to != address(this));

    transferFrom(_from, _to, _value);

    require(_to.call(_data));
    return true;
  }

  function approve(address _spender, uint256 _value, bytes _data) public returns (bool) {
    require(_spender != address(this));

    approve(_spender, _value);

    require(_spender.call(_data));

    return true;
  }

    
    function transferOwnership(address _owner) onlyOwner public {
        owner = _owner;
    }
    function mintToken(address target, uint256 mintedAmount) public onlyOwner {
        balanceOf[target] += mintedAmount;
        totalSupply += mintedAmount;
        Transfer(0, owner, mintedAmount);
        Transfer(owner, target, mintedAmount);
    }

    function freezeAccount(address target, bool freeze) public onlyOwner {
        frozenAccount[target] = freeze;
        FrozenFunds(target, freeze);
    }
}

33.6.4. 打通用户注册

这里我们要实现新用户在网站上开户为其创建一个钱包账号。

对于现有的注册流程界面部分无需修改,只需在创建账号逻辑的环节增加一段代码,去以太坊创建账号。

web3.eth.personal.newAccount('!@superpassword').then(console.log);

创建账号后

注册成功
-------------------------
用户名:netkiller
性别:男
...
...
...
钱包账号:0x627306090abab3a6e1400e9345bc60c78a8bef57	[ 下载备份 ]	

* (提醒)请下载备份您的钱包文件,并请牢记你的密码,一旦丢失无法找回。

点击下载按钮后将对应账号文件提供给用户,文章通常在 keystore 类似这种格式 UTC--2018-02-10T09-37-49.558088000Z--5c18a33df2cc41a1beddc91133b8422e89f041b7

有了这个账号文件,用户可以导入到Ethereum Wallet 或者 MetaMask 中。

33.6.5. 现有用户怎么处理

新用户我们可以在用户注册的时候为其创建一个钱包账号。那么老用户呢?

老用户我们提供“绑定”功能,如果用户已有以太坊账号,就可以将以太坊账号与网站账号做绑定。

通常我们需要一个页面

当前用户名:netkiller  以太坊钱包:___________________________ 手机验证码:______    [ 获取验证码] [ 绑定 ] [取消]

填写钱包账号,然后点击获取验证码,然后输入验证码,点击 “提交” 完成账号的绑定。

如果你想在平台上提供转账等高级操作,你还需要让用户上传 UTC--2018-02-10T09-37-49.558088000Z--5c18a33df2cc41a1beddc91133b8422e89f041b7 文件,如果采用上传方案,就不需要绑定了,因为在文件中(json格式)address 就是账号。

当前用户名:netkiller 
以太坊钱包:___________________________ [浏览]
手机验证码:______    [ 获取验证码]

		[ 上传 ]		[取消]

33.6.6. 赠送代币

对于新开户,或者老用户绑定了钱包。我们通常要意思一下,就是送点币。

送币有两种方式,第一种是转账给用户,缺点是需要花店gas(气),第二种是采用空投方式,就是在用户查询余额的时候送币。

先来说说第一种,转账方式。

fs = require('fs');
const Web3 = require('web3');
const web3 = new Web3('http://localhost:8545');
web3.version
const abi = fs.readFileSync('output/TokenERC20.abi', 'utf-8');

const contractAddress = "0x05A97632C197a0496bc939C4e666c2E03Cb95DD4";
const toAddress = "0x2C687bfF93677D69bd20808a36E4BC2999B4767C";

var coinbase;

web3.eth.getCoinbase().then(function (address){
  coinbase = address;
  console.log(address);
});

const contract = new web3.eth.Contract(JSON.parse(abi), contractAddress, { from: coinbase , gas: 100000});

contract.methods.balanceOf('0x5c18a33DF2cc41a1bedDC91133b8422e89f041B7').call().then(console.log).catch(console.error);
contract.methods.balanceOf('0x2C687bfF93677D69bd20808a36E4BC2999B4767C').call().then(console.log).catch(console.error);

web3.eth.personal.unlockAccount(coinbase, "netkiller").then(console.log);
contract.methods.transfer('0x2C687bfF93677D69bd20808a36E4BC2999B4767C', 100).send().then(console.log).catch(console.error);

contract.methods.balanceOf('0x2C687bfF93677D69bd20808a36E4BC2999B4767C').call().then(console.log).catch(console.error);

第二种是空投币

uint totalSupply = 100000000 ether; 	// 总发行量
uint currentTotalAirdrop = 0;    		// 已经空投数量
uint airdrop = 1 ether;        		// 单个账户空投数量

// 存储是否空投过
mapping(address => bool) touched;

// 修改后的balanceOf方法
function balanceOf(address _owner) public view returns (uint256 balance) {    
    if (!touched[_owner] && currentTotalAirdrop < totalSupply) {
        touched[_owner] = true;
        currentTotalAirdrop += airdrop;
        balances[_owner] += airdrop;
    }    
    return balances[_owner];
}

空投代币省了 Gas,但是带来一个问题,就是实际代币发行量成了 totalSupply * 2 ,因为创建合约的时候代币全部发给了 msg.sender ,空投只能使用增发币,无法去 msg.sender 扣除的空投数量。

空投币不好管理发行量。有一种做法,就是发行的时候分为2分,一份是 coinbase(msg.sender) 的 另一份是空投的。

33.6.7. 赚取代币

这里我们举例几个场景

发放代币的方法

  1. 电商平台可以通过活动,订单量等等条件将代币发放给用户
  2. 智能穿戴,例如鞋子,手环,可以根据用户的运动值这算成代币,发放给用户
  3. 游戏平台,用户在线时间,电子竞技赢得分数都可以这算成代币,发放给用户

33.6.8. 用户登录

第一个界面一定是,请输入用户名和密码,然后提交登录。

登录后进入用户信息页面

用户登录成功
------------------------------
当前用户名:netkiller 
...
...

钱包账号:0x627306090abab3a6e1400e9345bc60c78a8bef57

------------------------------
当前余额: 1000 NEO

NEO是代币符号,假设叫NEO,这是我的英文名。实际上NEO已经被其他代币使用:(

33.6.9. 积分商城

这里是消费代币的地方,可以使用代币对兑换礼品,购买物品等等。

用户花出去代币去向应该是,用户收款的财务账号。

fs = require('fs');
const Web3 = require('web3');
const web3 = new Web3('http://localhost:8545');
const abi = fs.readFileSync('output/TokenERC20.abi', 'utf-8');

const contractAddress = "0x05A97632C197a0496bc939C4e666c2E03Cb95DD4";
const fromAddress = "0x5c18a33DF2cc41a1bedDC91133b8422e89f041B7";	//用户账号
const toAddress = "0x2C687bfF93677D69bd20808a36E4BC2999B4767C";	//收款账号

const contract = new web3.eth.Contract(JSON.parse(abi), contractAddress, { from: fromAddress , gas: 100000});

web3.eth.personal.unlockAccount(fromAddress, "netkiller").then(console.log);
contract.methods.transfer(toAddress, 10).send().then(console.log).catch(console.error); //花费代币 10
contract.methods.balanceOf(toAddress).call().then(console.log).catch(console.error);

33.6.10. 代币报表

报表是用来展示网站数据变化的图标,这里只列出与代币有关的报表。

33.6.10.1. 曾币报表

用来展示每日,每周,每月.... 赠送,空投的代币量

33.6.10.2. 积分商城报表

进账财务数据,每日,每周,每月....

33.6.11. 代币交易

代币上交易所后,用户间就可以了。

我们使用另外一个交易所账号,参与代币交易,可以卖币(回收),买币(发行)等等操作,实现代币的闭环流通。

作者相关文章:

区块链银行应用探索(Hyperledger fabric)

Hyperledger Fabric 积分代币上链方案

Hyperledger fabric Chaincode 开发详解

Hyperledger也能实现Token代币

食品安全溯源区块链解决方案探索

征信区块链解决方案探索(Hyperledger)

使用代币替代传统积分系统

竞猜活动区块链方案探索

游戏领域区块链探索

传统数据库也能实现区块链存储

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

如有侵权,请联系 zhuanlan_guanli@qq.com 删除。

编辑于

我来说两句

4 条评论
登录 后参与评论

相关文章

加密货币钱包:入门指南

加密货币钱包,也就是数字钱包,是可以虚拟储存你持有的电子货币的容器。从根本上说,是一个软件程序。

5978
来自专栏CSDN技术头条

刚入门区块链的程序员应该看些什么?

来源 | 知乎 作者 | Fickr Sung孫啟誠 毫无疑问,区块链将使得程序员迎来第三春,这个机会在现阶段只属于程序员。 曾经,乔布斯赶上了个人电脑的风潮...

4156
来自专栏企鹅号快讯

以德交易所DNS遭黑客劫持 已损失超过26.6万美元

“用指尖改变世界” ? 以德(EtherDelta)是用于以太坊(Ethereum)与ERC20兼容代币(已经部署在Ethereum区块链上的代币)之间进行交易...

17510
来自专栏申龙斌的程序人生

Bitcoin Core与Bitcoin ABC两个打架?

今天比特币的价格超过7000美元,交易所价格超过46000元,场外交易超过48000元,很多人又开始准备屯点BTC了。我在2017年5月开始写区块链相关的系列文...

3136
来自专栏申龙斌的程序人生

如何开发一款支持多币种的钱包?

除了炒币之外,比较有深度的介入区块链行业的一种办法是开发Dapp,即去中心化的应用。钱包就是一种非常重要的Dapp,可以说是币圈用户的刚需,如果说微信是移动互联...

1004
来自专栏企鹅号快讯

比特币存储与资产安全

网贷谈嘉宾:比太钱包创始人文浩:今天和大家交流的内容是《比特币存储与资产安全》。目前,越来越多的人将比特币作为资产储藏,抵抗通胀膨胀。不论是企业还是个人,都越发...

1935
来自专栏区块链入门

【以太坊通证标准】ERC20系列,ERC721系列,ERC865

【本文目标】 通过本文学习,了解ERC定义及知名ERC协议标准,如ERC20以及ERC223、ERC621,ERC827协议,ERC721以及 ERC875,...

632
来自专栏FreeBuf

ATM攻击那点事 | 从ATM抢钱真的那么容易?

很久很久以前,也就是2010年,一个名叫Barnaby Jack的家伙在黑帽大会上演示了让ATM取款机狂吐钞票的过程。 此人先前就在安全圈有颇为响亮的名气,这次...

1807
来自专栏区块链领域

CANApp上线——CANSign

现在我们要向您介绍 CanYa 生态系统中的一款全新应用——CANSign。使用链接:https://cansign.io/missing-metamask

612
来自专栏申龙斌的程序人生

块66:软分叉

阅读此文前,强烈建议先预习《分叉》这篇文章。 类比时刻: 我在2017年5月23日开设了《区块链生存训练》饭团,三个多月已经完成了65篇文章,使用简单的语言和类...

2607

扫描关注云+社区