在上一篇文章中我們掌握了如何發(fā)行自己的ERC20 Token。如果只是單純發(fā)一個Token是沒有任何意義的,Token應(yīng)該是可以跟我們的產(chǎn)品運(yùn)營結(jié)合,用于用戶激勵、產(chǎn)品推廣等方面。就像交易所平臺的平臺幣一樣,既可以用來抵消交易費(fèi)用,又可以用于投票等。這也是目前token真正有使用到的方式吧,就是當(dāng)做平臺的一種資產(chǎn),這種資產(chǎn)是基于區(qū)塊鏈的,無法串改的。而平臺的運(yùn)行還是中心化的運(yùn)行方式,就像幣乎一樣。
那么在平臺上的Token到底是怎么流通的呢?以幣乎為例,幣乎的key是基于以太坊的ERC20 Token。我們知道在幣乎上用戶點贊會有獎勵,評論點贊也有獎勵,如果這種結(jié)算都放在了以太坊上來做的話,一個是速度可能會很慢,另一個結(jié)算的費(fèi)用也會非常高。所以我們可以看到幣乎里面有還未開放的充幣,提幣功能。這就是說,你只有提幣的時候,幣乎才會把你有的幣轉(zhuǎn)到你的以太坊錢包地址上。充幣的話,幣乎會給你一個以太坊地址,你把幣轉(zhuǎn)到這個地址上就行。這個做法應(yīng)該是跟交易所的做法一樣。那么前面提到的點贊獎勵是怎么處理的呢?我的理解是,幣乎在結(jié)算的時候會在你幣乎賬號key總數(shù)加上獎勵給你的key,只是簡單的數(shù)據(jù)庫修改下數(shù)據(jù),并不會涉及token的轉(zhuǎn)讓流通。
那么站在程序角度來看,我們應(yīng)該怎么用代碼來實現(xiàn)Token的充token、提t(yī)oken操作呢?
以太坊提供了非常好用的工具來跟以太坊通信,web3.js 就是其中之一。下面我就介紹如果使用 web3.js實現(xiàn)token的充token,提t(yī)oken。
下面的內(nèi)容假設(shè)您已經(jīng)了解以太坊、智能合約、solidity以及web3.js。不懂的話,建議先去了解下,網(wǎng)上已經(jīng)有非常好的文章介紹了,我這里就不重復(fù)說明了。
首先你得在本地安裝好 node.js(這個也不多做介紹)。
首先介紹下ganache。 ganache是一個基于內(nèi)存的以太坊鏈,用于本地測試,而不用去連接測試網(wǎng)絡(luò),省去同步以太坊區(qū)塊的麻煩。安裝也很簡單,命令行執(zhí)行 npm install -g ganache-cli。
安裝完成后,執(zhí)行ganache-cli,啟動網(wǎng)絡(luò)。
之前的文章,把Token發(fā)布到了ropsten測試網(wǎng)絡(luò)上,這次我們選擇本地環(huán)境,把token發(fā)布到我們本地網(wǎng)絡(luò)上。首先在metamask中網(wǎng)絡(luò)選擇自定義網(wǎng)絡(luò),如下圖:

確定后,在把我們的MFC Token重新發(fā)布一遍(參見上一篇文章)。
好了,我們已經(jīng)把Token發(fā)布到了本地網(wǎng)絡(luò)了。接下來使用web3.js來調(diào)用本地測試網(wǎng)絡(luò)。
為了方便測試,我是直接在頁面上調(diào)用本地測試網(wǎng)絡(luò),真正的做法應(yīng)該是調(diào)用服務(wù)器接口,服務(wù)器內(nèi)有相應(yīng)的服務(wù)調(diào)用以太坊網(wǎng)絡(luò)。這里只是為了方便展示才這樣做的,大家不要以這樣的方式上線。
我們可以總結(jié)出幾個必須有的功能: 1 查詢余額, 2 Token提現(xiàn),3 充Token,4 賬戶之間轉(zhuǎn)以太。
首先實現(xiàn)如何查詢賬戶余額的功能,包括以太坊余額和MFC余額??慈缦麓a。
// 首先在頁面引入我們需要使用的js
<script src="./web3.min.js"></script>
<script src="./ethereumjs-wallet-0.6.0.min.js"></script>
<script>
// 初始化web3 對象
if (typeof web3 !== 'undefined') {
web3 = new Web3(web3.currentProvider);
} else {
// set the provider you want from Web3.providers
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}
// 使用本地測試網(wǎng)絡(luò)的第一個賬戶為默認(rèn)賬戶
web3.eth.defaultAccount = web3.eth.accounts[0];
// 從remix compile tab頁中MyFreeCoin的 detail中復(fù)制 abi信息到 contract() 方法內(nèi),不懂remix的請看前面那邊文章
var mfcContract = web3.eth.contract([{...}]);
// 從remix run tab頁發(fā)布的合約上復(fù)制合約地址
var contractAddress = '0x09901f8fdf2265d9be48c7877161a2f6c2e503e8';
// 初始化我們的合約對象
var contract = mfcContract.at(contractAddress);
// 獲取我們需要查詢的以太坊地址
var address = document.getElementById('address').value;
if(!web3.isAddress(address)){ // 檢測地址是合法的以太坊地址
alert("Invalid address!");
return ;
}
// 查詢以太坊地址的 eth余額
web3.eth.getBalance(address, function(err, resp){
if(!err){
eth.innerHTML = web3.fromWei(resp, 'ether').toNumber();
}else{
console.log(err);
}
});
// 查詢MFC的余額
contract.balanceOf(address, (err, tkns) => {
if (!err) {
mfc.innerHTML = web3.fromWei(tkns, 'ether').toNumber()
}else{
console.log(err)
}
})
</script>
提Token。 提Token的操作是,我們提供自己的以太坊地址給平臺,平臺給這個地址打 Token。 實現(xiàn)代碼如下:
// 獲取提t(yī)oken的以太坊地址
var reciver = document.getElementById('reciver').value;
// 提t(yī)oken數(shù)量
var amount = document.getElementById('amount').value;
if(!web3.isAddress(reciver)){ // 檢測地址是否合法
alert("reciver: Invalid address!");
return ;
}
// 發(fā)送token,注意這里默認(rèn)是從本地測試網(wǎng)絡(luò)的第一個賬戶轉(zhuǎn)token過去,因為我們的默認(rèn)賬戶就是它 web3.eth.defaultAccount = web3.eth.accounts[0]; 同時在remix發(fā)布合約的時候,我們是使用第一個賬戶發(fā)布的,也就是說第一個賬戶開始擁有所有的MFC Token
contract.transfer(reciver, web3.toWei(amount), function(err, resp){
if(!err){
console.log(resp);
alert('success');
}else{
console.log(err);
}
})
這就實現(xiàn)了從發(fā)布合約的默認(rèn)賬戶轉(zhuǎn)token到用戶地址的功能。但是這么做是比較危險的,因為您把所有的token都放到了一個聯(lián)網(wǎng)的賬戶中,如果平臺被黑客入侵,就有可能把您所有的Token轉(zhuǎn)走,風(fēng)險會比較大
。所以正常的做法應(yīng)該是,把包含大量token的賬戶保存在冷錢包里面,只在需要使用的時候才從冷錢包里面提Token到運(yùn)營的賬戶地址中。那就是說我們需要實現(xiàn)兩個地址之間轉(zhuǎn)token的功能,不能像上面那樣默認(rèn)使用發(fā)Token的賬戶來轉(zhuǎn)Token了。那么我們應(yīng)該怎么來實現(xiàn)呢?看下面的代碼
// 假設(shè)這個是我們的運(yùn)營錢包地址
var from = '0x009ddafb6dd10f2ed72dd0d0c7f291b5a0cea9eb';
// 轉(zhuǎn)token的數(shù)量
var amount = document.getElementById('mAmount').value;
// 提t(yī)oken的地址
var to = document.getElementById('mTo').value;
if(!web3.isAddress(to)){
alert("Invalid to address");
return ;
}
// 獲取當(dāng)前交易數(shù),當(dāng)nonce使用
web3.eth.getTransactionCount(from, function(err, count){
if(!err){
var tcount = count;
// 先檢查當(dāng)前運(yùn)營賬戶的余額夠不夠轉(zhuǎn)token
contract.balanceOf(from, (err, tkns) => {
if (!err) {
var balance = web3.fromWei(tkns, 'ether').toNumber();
if(balance < amount){
alert('You do not get enough mfc token!');
return ;
}
amount = web3.toWei(amount);
// 交易信息
var rawTransaction = {
"from": from,
"nonce": "0x" + tcount.toString(16),
"gasPrice": "0x003B9ACA00",
"gasLimit": "0x250CA",
"to": contractAddress,
"value": "0x0",
"data": contract.transfer(to, amount).encodeABI()
};
// 運(yùn)營錢包地址的私鑰,需要用它來簽名交易
var privKey = new Buffer("0x811502c101ae015b658dccbdb6626840b734c40ba229ce78877826c27ca07513", 'hex');
// 對交易簽名
var tx = new ethereumjs.Tx(rawTransaction);
tx.sign(privKey);
var serializedTx = tx.serialize();
// 發(fā)送交易到以太坊
web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'),function(err, result){
if(!err){
console.log(result);
}else{
console.log(err);
}
});
}else{
console.log(err)
}
})
}else{
console.log(error);
}
這樣就可以實現(xiàn)不同賬戶之間的轉(zhuǎn)Token操作了。
充Token。首先我們要理解下充Token,是用戶從他錢包地址轉(zhuǎn)Token到我們平臺的賬戶地址。所以首先我們得給用戶提供一個跟他對應(yīng)的錢包地址,同時要監(jiān)控用戶的轉(zhuǎn)Token事件,當(dāng)用戶轉(zhuǎn)Token過來了,我們需要記錄,同時在數(shù)據(jù)庫中把用戶的Token余額增加。所以我們得先搞定給用戶提供一個錢包地址,這里我們使用到了Ethereum-Wallet來幫我們生成地址,公鑰,私鑰。代碼如下:
// 上面已經(jīng)引入了Ethereum-Wallet相應(yīng)的js
// 用戶生成地址的密碼
var password = document.getElementById('password').value;
// 初始化錢包對象
var wallet = ethereumjs.Wallet.generate(password);
// 獲取私鑰
document.getElementById('gPrikey').innerHTML = "private key:" + wallet.getPrivateKeyString();
//獲取公鑰
document.getElementById('gPubkey').innerHTML = "public key:" + wallet.getPublicKeyString();
// 獲取地址
document.getElementById('gAddress').innerHTML = "address:" + wallet.getAddressString();
但是還有一個問題,當(dāng)用戶轉(zhuǎn)token過來,我們怎么知道他有轉(zhuǎn)呢?在我們的Token只能合約里面每次轉(zhuǎn)Token的都會觸發(fā)Transfer事件(solidity知識),我們只需要監(jiān)聽這個事件就可以了,代碼如下:
// 監(jiān)聽 轉(zhuǎn)token事件
contract.Transfer( function(err, res) {
if(!err){
console.log(res);
// 發(fā)送Token的地址
var from = res.args._from;
// 接受Token的地址 所以我們只需要對比下 接受Token的地址,是我們自己的地址,那就可以給相應(yīng)的用戶增加充token記錄了。
var to = res.args._to;
var amount = web3.fromWei(res.args._value).toNumber();
var html = '<p>From: '+ from+';To: '+ to+';Amount: '+amount+'</p>';
document.getElementById('logs').insertAdjacentHTML('beforeend', html);
}else{
console.log(err);
}
})
賬戶之間發(fā)送eth。有一些新生成的賬戶開始是沒有eth的,我們需要給它轉(zhuǎn)的eth,以供它使用,代碼如下:
// 接受eth地址
var etherAdress = document.getElementById('etherAdress').value;
if(!web3.isAddress(etherAdress)){
alert('Invalid address');
return ;
}
// 發(fā)送交易, 轉(zhuǎn)eth,這里使用的默認(rèn)賬戶轉(zhuǎn)eth,如果想使用另外的地址轉(zhuǎn)也跟 轉(zhuǎn)token一樣,使用私鑰來簽名交易即可
web3.eth.sendTransaction({
to: etherAdress,
value: '1000000000000000000'
},function(err, result){
if(!err){
console.log(result);
}else{
console.log(err);
}
})
這樣Token之間的流通代碼實現(xiàn)已經(jīng)完成了,具體的代碼實現(xiàn)可以在github上查看。
上面提到的肯定還有不足的地方,可能我理解也是錯誤的,也請大家指出錯誤的地方。大家共同進(jìn)步。
Token已經(jīng)知道了如何流通了,但是艾西歐又是怎么一回事呢?為何給一個地址轉(zhuǎn)eth就可以得到Token呢?如何發(fā)布一個艾西歐合約呢?這也是后面我會繼續(xù)研究的,自己發(fā)行一個艾西歐來玩玩,當(dāng)然是在測試環(huán)境!
2018/03/17 更新
有同學(xué)指出賬戶間的轉(zhuǎn)賬有問題,經(jīng)過我的測試發(fā)現(xiàn)確實是存在一個問題,就是賬戶之間轉(zhuǎn)賬的時候,默認(rèn)轉(zhuǎn)的還是web3.eth.defaultAccout 賬戶的token,而沒有轉(zhuǎn)經(jīng)過私鑰簽名的賬戶。
后面查看web3.js 的文檔,文檔的web3.js 對應(yīng)的版本是1.0,但是我們使用的還是0.x版本。 而且1.0版本的api 跟之前的版本變化非常大。
我重新使用web3.js 1.x 的版本重新實現(xiàn)了一遍。 測試用戶間的轉(zhuǎn)賬是沒有問題的。修改后的代碼已經(jīng)上傳到github, 參考文件 mfc2.html。