| 知乎專欄 | 多維度架構 | | | 微信號 netkiller-ebook | | | QQ群:128659835 請註明“讀者” |
首先我們使用代幣是為了取代傳統的積分機制。因為代幣的“幣”特性能夠實現流通,交易等等,實現一個閉環生態系統。而傳統的積分只能內部使用,無法流通,外接不承認加分的價值,積分無法交易,流通。
其次我們並非是為了ICO炒作,然後割韭菜,代幣取代積分是剛性需求。
這裡假設您已經看我我之前寫的文章,知道什麼是代幣,並且能用錢包部署代幣合約。例如現在合約已經部署到主網,已經可以使用錢包轉賬代幣,甚至代幣已經上了交易所,接下來我們要做什麼呢?
接下來的工作是將代幣和網站或者手機App打通,只有將代幣整合到現有業務場景中代幣才有意義。
我認為我們需要下面幾種角色的賬號
代幣發行賬號,負責發行代幣,管理代幣
收款賬號,用戶代幣流通中的收款,這個賬號應該由財務人員負責,相當於企業對公賬號。
交易所賬號,用來對接交易所
用戶賬號,普用戶的賬號,依賴接受代幣,消費代幣,交易代幣。
如果着手一個遊戲項目上鏈,我們需要怎麼做呢?
上鏈步驟
收集需求,收集公司的內部上鏈需求,聽取所有部門的建議和訴求。
收集內容例如,代幣發行量多少?代幣小數位數,代幣名稱,是否會增發,是否需要凍結,代幣怎樣流通,怎樣回收
Dapp 的 UI 設計,各種功能流程
分析需求,因為需求來自各種部門,各種崗位等等,他們不一定從事需求分析工作,所以我們需求對他們的需求過濾,分析,然後給出初步的PRD文檔(產品需求文檔)
根據收集的需求設計合約和Dapp
根據需求設計Dapp
系統架構設計,軟件架構設計,技術選型;需要考慮擴展性,靈活性,並發設計,數據安全,部署,後期監控,各種埋點(統計、監控)
準備環境,我們需要三個環境,開發,測試,生產(運營)。
項目啟動
運維部門準備環境,開始建設監控系統
開發部門開發合約和Dapp
測試部門準備測試用例,測試環境
測試
Alpha 階段,將合約部署到測試環境,測試合約的每個函數的工作邏輯,確保無誤。因為合約一旦部署將不能更改,只能廢棄使用新的合約,所以這個測試步驟必須重視。
Beta 階段,將測試合約部署到測試網,例如 Ropsten ,可以邀請公司內部測試
部署生產環境
部署合約,將合約部署到主網
Dapp 部署到生產環境。
驗收測試,在生產環境做最後驗收測試
代幣上交易所
合約部署這里加就不介紹了,可以參考《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});
contract.methods.balanceOf(toAddress).call().then(console.log).catch(console.error);
這裡是消費代幣的地方,可以使用代幣對兌換禮品,購買物品等等。
用戶花出去代幣去嚮應該是,用戶收款的財務賬號。
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);
報表是用來展示網站數據變化的表徵圖,這裡只列出與代幣有關的報表。