本文节选自电子书《Netkiller Blockchain 手札》
中国广东省深圳市龙华新区民治街道溪山美地 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 |
首先我们使用代币是为了取代传统的积分机制。因为代币的“币”特性能够实现流通,交易等等,而传统的积分只能内部使用,无法流通,外接不承认加分的价值,积分无法交易,流通。
其次我们并非是为了ICO炒作,然后割韭菜,代币取代积分是刚性需求。
这里假设您已经看我我之前写的文章,知道什么是代币,并且能用钱包部署代币合约。例如现在合约已经部署到主网,已经可以使用钱包转账代币,甚至代币已经上了交易所,接下来我们要做什么呢?
接下来的工作是将代币和网站或者手机App打通,只有将代币整合到现有业务场景中代币才有意义。
我认为我们需要下面几种角色的账号
日志
监控
监控内容
如果着手一个游戏项目上链,我们需要怎么做呢?
上链步骤
合约部署这里加就不介绍了,可以参考《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);
}
}
这里我们要实现新用户在网站上开户为其创建一个钱包账号。
对于现有的注册流程界面部分无需修改,只需在创建账号逻辑的环节增加一段代码,去以太坊创建账号。
web3.eth.personal.newAccount('!@superpassword').then(console.log);
创建账号后
注册成功
-------------------------
用户名:netkiller
性别:男
...
...
...
钱包账号:0x627306090abab3a6e1400e9345bc60c78a8bef57 [ 下载备份 ]
* (提醒)请下载备份您的钱包文件,并请牢记你的密码,一旦丢失无法找回。
点击下载按钮后将对应账号文件提供给用户,文章通常在 keystore 类似这种格式 UTC--2018-02-10T09-37-49.558088000Z--5c18a33df2cc41a1beddc91133b8422e89f041b7
有了这个账号文件,用户可以导入到Ethereum Wallet 或者 MetaMask 中。
新用户我们可以在用户注册的时候为其创建一个钱包账号。那么老用户呢?
老用户我们提供“绑定”功能,如果用户已有以太坊账号,就可以将以太坊账号与网站账号做绑定。
通常我们需要一个页面
当前用户名:netkiller 以太坊钱包:___________________________ 手机验证码:______ [ 获取验证码] [ 绑定 ] [取消]
填写钱包账号,然后点击获取验证码,然后输入验证码,点击 “提交” 完成账号的绑定。
如果你想在平台上提供转账等高级操作,你还需要让用户上传 UTC--2018-02-10T09-37-49.558088000Z--5c18a33df2cc41a1beddc91133b8422e89f041b7 文件,如果采用上传方案,就不需要绑定了,因为在文件中(json格式)address 就是账号。
当前用户名:netkiller
以太坊钱包:___________________________ [浏览]
手机验证码:______ [ 获取验证码]
[ 上传 ] [取消]
对于新开户,或者老用户绑定了钱包。我们通常要意思一下,就是送点币。
送币有两种方式,第一种是转账给用户,缺点是需要花店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) 的 另一份是空投的。
这里我们举例几个场景
发放代币的方法
第一个界面一定是,请输入用户名和密码,然后提交登录。
登录后进入用户信息页面
用户登录成功
------------------------------
当前用户名:netkiller
...
...
钱包账号:0x627306090abab3a6e1400e9345bc60c78a8bef57
------------------------------
当前余额: 1000 NEO
NEO是代币符号,假设叫NEO,这是我的英文名。实际上NEO已经被其他代币使用:(
这里是消费代币的地方,可以使用代币对兑换礼品,购买物品等等。
用户花出去代币去向应该是,用户收款的财务账号。
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);
报表是用来展示网站数据变化的图标,这里只列出与代币有关的报表。
用来展示每日,每周,每月.... 赠送,空投的代币量
进账财务数据,每日,每周,每月....
代币上交易所后,用户间就可以了。
我们使用另外一个交易所账号,参与代币交易,可以卖币(回收),买币(发行)等等操作,实现代币的闭环流通。
作者相关文章:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。