簡單代幣開發(fā)
代幣(Token):
代幣單純從其名字上理解的話,就是一種可以替代通用貨幣起到交換媒介作用的東西,可以是商場(chǎng)積分,可以是游戲幣,也可以是賭場(chǎng)籌碼。但是在區(qū)塊鏈中,就不完全是那么回事了,區(qū)塊鏈中的代幣或者說Token通常指的是具有流通性的加密數(shù)字權(quán)益證明,例如比特幣、以太幣等數(shù)字貨幣都屬于代幣
從以上定義可以得知代幣的三個(gè)要素:
- 權(quán)益證明:一種數(shù)字形式存在的權(quán)益憑證,代表一種權(quán)利,一種固有的內(nèi)在價(jià)值和使用價(jià)值
- 加密:為了防止篡改,保護(hù)隱私,不可以復(fù)制等
- 較高的可流通性(去中心化):可以進(jìn)行交易,兌換通用或法定貨幣等,之所以要去中心化是因?yàn)橹行幕拇鷰哦即嬖谄脚_(tái)限制及信任問題,所以其流通性就會(huì)受到局限
綜上,簡言之代幣的概念大致上就是基于區(qū)塊鏈發(fā)行的加密貨幣或者其他類似的東西。通常大多數(shù)人或團(tuán)隊(duì)在開發(fā)區(qū)塊鏈項(xiàng)目時(shí),都會(huì)考慮發(fā)行自己的代幣。在最初的時(shí)候,我們要發(fā)行自己的加密貨幣得從比特幣的源碼上改造出來。不過現(xiàn)如今通過以太坊平臺(tái),我們能夠很方便的開發(fā)并發(fā)行自己的代幣,所以本文將介紹如何基于以太坊開發(fā)自己的代幣。
本文將使用到兩個(gè)工具,分別是Remix和MetaMask,如果對(duì)這兩個(gè)工具不太了解的話可以參考我另外兩篇文章:
首先我們使用solidity開發(fā)一個(gè)擁有最基本功能的“代幣”demo,以便了解代幣最基礎(chǔ)的樣子,代碼如下:
pragma solidity ^0.4.20;
contract SimpleToken {
// 保存每個(gè)地址所擁有的代幣數(shù)量
mapping (address => uint256) public balanceOf;
// 參數(shù)為代幣的初始供應(yīng)量或者說發(fā)行數(shù)量
constructor (uint256 initSupply) public {
// 創(chuàng)建者擁有所有的代幣
balanceOf[msg.sender] = initSupply;
}
/**
* 轉(zhuǎn)移代幣
*
* @param _to 接收方的賬戶地址
* @param _value 轉(zhuǎn)移的代幣數(shù)量
*/
function transfer (address _to, uint256 _value) public {
// 發(fā)送方的賬戶余額需大于等于轉(zhuǎn)移的代幣數(shù)量
require(balanceOf[msg.sender] >= _value);
// 檢查有沒有發(fā)生溢出,因?yàn)閿?shù)量有可能超過uint256可存儲(chǔ)的范圍
require(balanceOf[_to] + _value >= balanceOf[_to]);
// 減少發(fā)送方賬戶的代幣數(shù)量
balanceOf[msg.sender] -= _value;
// 增加接收方賬戶的代幣數(shù)量
balanceOf[_to] += _value;
}
}
1.代碼編寫完成后,在remix上部署合約,首先初始化代幣供應(yīng)量:

2.查看指定賬戶所擁有的代幣數(shù)量:

3.轉(zhuǎn)移代幣到另一個(gè)賬戶地址:

4.此時(shí)該賬戶地址的代幣數(shù)量就只剩下100了:

ERC-20標(biāo)準(zhǔn)簡述
在上一小節(jié)中,我們實(shí)現(xiàn)了一個(gè)簡單的代幣,但就因?yàn)楹唵?,所以該代幣是極其不完善的,例如沒有代幣名稱、代幣符號(hào)以及無法控制代幣的交易等。
因此以太坊定義了ERC20,ERC20是以太坊定義的一個(gè)代幣統(tǒng)一標(biāo)準(zhǔn),該標(biāo)準(zhǔn)描述了我們?cè)趯?shí)現(xiàn)代幣時(shí)必須要遵守的協(xié)議,如指定代幣名稱、總量、實(shí)現(xiàn)代幣交易函數(shù)等,只有實(shí)現(xiàn)了該協(xié)議的代幣才能被以太坊錢包支持。
下圖是一個(gè)實(shí)現(xiàn)了ERC20協(xié)議的代幣:

實(shí)際上ERC20協(xié)議是定義了一組標(biāo)準(zhǔn)接口,該協(xié)議使得在以太坊上開發(fā)的任何代幣都能被其他應(yīng)用程序重用,例如錢包到分散的交易所等,如下:
contract ERC20Interface {
// 代幣名稱
string public constant name = "Token Name";
// 代幣符號(hào)或者說簡寫
string public constant symbol = "SYM";
// 代幣小數(shù)點(diǎn)位數(shù),代幣的最小單位, 18表示我們可以擁有 0.0000000000000000001單位個(gè)代幣
uint8 public constant decimals = 18; // 18 is the most common number of decimal places
// 查詢代幣的發(fā)行總量
function totalSupply() public constant returns (uint);
// 查詢地址的代幣余額
function balanceOf(address tokenOwner) public constant returns (uint balance);
// 查詢spender允許從tokenOwner上花費(fèi)的代幣數(shù)量
function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
// 實(shí)現(xiàn)代幣交易,用于從本賬戶給某個(gè)地址轉(zhuǎn)移代幣
function transfer(address to, uint tokens) public returns (bool success);
// 允許spender多次從你的賬戶取款,并且最多可取tokens個(gè),主要用于某些場(chǎng)景下授權(quán)委托其他用戶從你的賬戶上花費(fèi)代幣
function approve(address spender, uint tokens) public returns (bool success);
// 實(shí)現(xiàn)代幣用戶之間的交易,從一個(gè)地址轉(zhuǎn)移代幣到另一個(gè)地址
function transferFrom(address from, address to, uint tokens) public returns (bool success);
// 代幣交易時(shí)觸發(fā)的事件,即調(diào)用transfer方法時(shí)觸發(fā)
event Transfer(address indexed from, address indexed to, uint tokens);
// 允許其他用戶從你的賬戶上花費(fèi)代幣時(shí)觸發(fā)的事件,即調(diào)用approve方法時(shí)觸發(fā)
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}
ERC-20標(biāo)準(zhǔn)代幣的實(shí)現(xiàn)
代碼如下:
pragma solidity ^0.4.20;
// 定義ERC-20標(biāo)準(zhǔn)接口
contract ERC20Interface {
// 代幣名稱
string public name;
// 代幣符號(hào)或者說簡寫
string public symbol;
// 代幣小數(shù)點(diǎn)位數(shù),代幣的最小單位
uint8 public decimals;
// 代幣的發(fā)行總量
uint public totalSupply;
// 實(shí)現(xiàn)代幣交易,用于給某個(gè)地址轉(zhuǎn)移代幣
function transfer(address to, uint tokens) public returns (bool success);
// 實(shí)現(xiàn)代幣用戶之間的交易,從一個(gè)地址轉(zhuǎn)移代幣到另一個(gè)地址
function transferFrom(address from, address to, uint tokens) public returns (bool success);
// 允許spender多次從你的賬戶取款,并且最多可取tokens個(gè),主要用于某些場(chǎng)景下授權(quán)委托其他用戶從你的賬戶上花費(fèi)代幣
function approve(address spender, uint tokens) public returns (bool success);
// 查詢spender允許從tokenOwner上花費(fèi)的代幣數(shù)量
function allowance(address tokenOwner, address spender) public view returns (uint remaining);
// 代幣交易時(shí)觸發(fā)的事件,即調(diào)用transfer方法時(shí)觸發(fā)
event Transfer(address indexed from, address indexed to, uint tokens);
// 允許其他用戶從你的賬戶上花費(fèi)代幣時(shí)觸發(fā)的事件,即調(diào)用approve方法時(shí)觸發(fā)
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}
// 實(shí)現(xiàn)ERC-20標(biāo)準(zhǔn)接口
contract ERC20Impl is ERC20Interface {
// 存儲(chǔ)每個(gè)地址的余額(因?yàn)槭莗ublic的所以會(huì)自動(dòng)生成balanceOf方法)
mapping (address => uint256) public balanceOf;
// 存儲(chǔ)每個(gè)地址可操作的地址及其可操作的金額
mapping (address => mapping (address => uint256)) internal allowed;
// 初始化屬性
constructor() public {
name = "Test Token";
symbol = "TEST";
decimals = 18;
totalSupply = 100000000;
// 初始化該代幣的賬戶會(huì)擁有所有的代幣
balanceOf[msg.sender] = totalSupply;
}
function transfer(address to, uint tokens) public returns (bool success) {
// 檢驗(yàn)接收者地址是否合法
require(to != address(0));
// 檢驗(yàn)發(fā)送者賬戶余額是否足夠
require(balanceOf[msg.sender] >= tokens);
// 檢驗(yàn)是否會(huì)發(fā)生溢出
require(balanceOf[to] + tokens >= balanceOf[to]);
// 扣除發(fā)送者賬戶余額
balanceOf[msg.sender] -= tokens;
// 增加接收者賬戶余額
balanceOf[to] += tokens;
// 觸發(fā)相應(yīng)的事件
emit Transfer(msg.sender, to, tokens);
}
function transferFrom(address from, address to, uint tokens) public returns (bool success) {
// 檢驗(yàn)地址是否合法
require(to != address(0) && from != address(0));
// 檢驗(yàn)發(fā)送者賬戶余額是否足夠
require(balanceOf[from] >= tokens);
// 檢驗(yàn)操作的金額是否是被允許的
require(allowed[from][msg.sender] <= tokens);
// 檢驗(yàn)是否會(huì)發(fā)生溢出
require(balanceOf[to] + tokens >= balanceOf[to]);
// 扣除發(fā)送者賬戶余額
balanceOf[from] -= tokens;
// 增加接收者賬戶余額
balanceOf[to] += tokens;
// 觸發(fā)相應(yīng)的事件
emit Transfer(from, to, tokens);
success = true;
}
function approve(address spender, uint tokens) public returns (bool success) {
allowed[msg.sender][spender] = tokens;
// 觸發(fā)相應(yīng)的事件
emit Approval(msg.sender, spender, tokens);
success = true;
}
function allowance(address tokenOwner, address spender) public view returns (uint remaining) {
return allowed[tokenOwner][spender];
}
}
將以上代碼復(fù)制到Remix上,編譯合約代碼:

部署合約:

部署成功:

測(cè)試balanceOf方法:

測(cè)試transfer方法:

在Remix上測(cè)試完基本的方法后,我們通過MetaMask在以太坊的測(cè)試網(wǎng)絡(luò)上發(fā)行我們的代幣。打開MetaMask選擇Ropsten測(cè)試網(wǎng)絡(luò),如下:

注:如果你沒有余額請(qǐng)點(diǎn)擊DEPOSIT按鈕,然后再點(diǎn)擊GET ETHER按鈕,會(huì)進(jìn)入一個(gè)網(wǎng)站,在該網(wǎng)站中點(diǎn)擊request 1 ether from faucet,可以送一些測(cè)試網(wǎng)絡(luò)的以太幣給你:

然后到Remix IDE上刷新一下,此時(shí)Remix會(huì)自動(dòng)選擇相應(yīng)的測(cè)試網(wǎng)絡(luò),注意MetaMask和Remix需要在同一個(gè)瀏覽器上,并且Environment和Account和MetaMask保持一致,如下:

點(diǎn)擊Deploy后,這時(shí)MetaMask會(huì)彈出一個(gè)交易確認(rèn)框,點(diǎn)CONFIRM:

待合約部署交易確認(rèn)之后,復(fù)制合約地址:

打開Metamask界面,打開菜單(Menu),點(diǎn)ADD TOKENS:

出現(xiàn)如下對(duì)話框,選擇Custom Token,然后將合約地址粘貼進(jìn)去:

點(diǎn)ADD TOKENS:

這時(shí)在MetaMask里就可以看到我們創(chuàng)建的代幣了,如下:

接下來我們測(cè)試一下轉(zhuǎn)移代幣,選擇我們剛剛部署的代幣,點(diǎn)擊SEND:

填寫轉(zhuǎn)賬的相關(guān)信息:

確認(rèn)轉(zhuǎn)賬:

成功后會(huì)生成相應(yīng)的交易記錄:

如此一來我們就完成了代幣的創(chuàng)建和部署(正式網(wǎng)絡(luò)和測(cè)試網(wǎng)絡(luò)部署方法一樣),現(xiàn)在已經(jīng)可以在Etherscan查詢到我們剛剛發(fā)行的代幣了:

查看代幣詳情:

關(guān)于發(fā)行總量為0的問題
在上一小節(jié)中,我們已經(jīng)成功在以太坊上發(fā)行了我們自己的代幣,但許多同學(xué)應(yīng)該也已經(jīng)產(chǎn)生了一個(gè)疑問,為啥明明代幣中設(shè)置了代幣的發(fā)行數(shù)量,但是發(fā)行的數(shù)量還是為0呢?其實(shí)這也是新手在開發(fā)代幣時(shí)可能會(huì)遇到的一個(gè)坑,之所以我們看到發(fā)行總量為0是因?yàn)榇鷰诺陌l(fā)行總量的整數(shù)位表示并不是totalSupply的值,而是totalSupply除以10的decimals次方。
例如我們將decimals設(shè)置為8,那么就需要以代幣的發(fā)行總量(totalSupply)除以100000000,才能得到其整數(shù)。上一小節(jié)中合約代碼里設(shè)置的decimals值為18,代表代幣使用的小數(shù)位為18位,而totalSupply的值為1億,自然計(jì)算出來就是小于1的小數(shù),而且還是比較小的小數(shù),MetaMask只顯示3位小數(shù),所以導(dǎo)致在我們看來代幣的發(fā)行總量為0。
既然知道是什么原因?qū)е碌?,那么解決起來就好辦了,修改合約的構(gòu)造器代幣如下:
// 初始化屬性
constructor() public {
decimals = 18;
totalSupply = 100000000 * 10 ** uint256(decimals);
name = "Test Token";
symbol = "TEST";
// 初始化該代幣的賬戶會(huì)擁有所有的代幣
balanceOf[msg.sender] = totalSupply;
}
修改完成后,依舊按照我們上一小節(jié)所描述的流程去發(fā)行代幣并將代幣添加到MetaMask錢包中。此時(shí)該代幣的發(fā)行總量就應(yīng)該是我們期望的值了,如下:

我們也可以嘗試使用代幣進(jìn)行轉(zhuǎn)賬:

轉(zhuǎn)賬成功,該賬戶的余額也正??蹨p了:

另一個(gè)賬戶也正常收到了這100個(gè)代幣:

然后我們?cè)偃ゲ榭匆幌麓鷰?a target="_blank">詳情:
