1. 文章摘要
【本文目標】
通過逐步的指導和截圖舉證,一步步帶領一個技術小白完成一個數字貨幣(通證,代幣,TOKEN)的發布演示和上線交易。
【環境前置條件】
參考《第六課 技術小白如何開發一個DAPP區塊鏈應用(以寵物商店為例)》,已在本地WIDOWS環境完成MetaMask輕錢包客戶端的安裝和配置;作者建議最好遵循從頭開始的課程學習順序。不過如果你想半途插入實操學習,問題也不大,遇到障礙時反向找對應文章的指導內容即可完成。
【技術收獲】
從本實踐中,你可以學習到:
ERC20 Token的定義和實踐
使用Remix Solidity IDE編寫智能合約和編譯調試
使用MetaMask完成錢包賬戶查看
使用網頁錢包完成代幣交易演示
【實操課程列表】
第一課 如何在WINDOWS環境下搭建以太坊開發環境
第二課 如何實現以太坊最簡智能合約“Hello World”的運行
第四課 以太坊開發框架Truffle從入門到實戰
第六課 技術小白如何開發一個DAPP區塊鏈應用(以寵物商店為例)
第七課 技術小白如何在45分鐘內發行通證(TOKEN)并上線交易
第八課 如何調試以太坊官網的智能合約眾籌案例
第九課 如何在Remix環境下進行Solidity代碼單步調試
第十課 Solidity語言編輯器REMIX指導大全
【說明】未列出的課程為知識普及的非實操類課程,所有區塊鏈文章參考“區塊鏈入口”專欄。
2. ERC20 Token定義和接口說明
定義
ERC20合約是在2015年11月在EIP上提出的一個合約標準,代幣定義的一個標準。
Token代表數字資產,具有價值,但是并不是都符合特定的規范。
基于ERC20的貨幣更容易互換,并且能夠在Dapps上相同的工作。
新的標準可以讓token更兼容,允許其他功能,包括投票標記化。操作更像一個投票操作,Token的持有人可以完全控制資產,遵守ERC20的token可以跟蹤任何人在任何時間擁有多少token。基于eth合約的子貨幣,所以容易實施。
ERC20 Token接口說明
方法
注意:調用者必須處理返回false
的returns (bool success)
.調用者絕對不能假設返回false
的情況不存在。
name
返回這個令牌的名字,比如"MyToken"
.
可選 - 這種方法可以用來提高可用性,但接口和其他契約不能指望這些值存在。
function name() constant returns (string name)
symbol
返回令牌的符號,比如HIX
.
可選 - 這種方法可以用來提高可用性,但接口和其他契約不能指望這些值存在。
function symbol() constant returns (string symbol)
decimals
返回token使用的小數點后幾位, 比如 8
,表示分配token數量為100000000
可選 - 這種方法可以用來提高可用性,但接口和其他契約不能指望這些值存在。
function decimals() constant returns (uint8 decimals)
totalSupply
返回token的總供應量。
function totalSupply() constant returns (uint256 totalSupply)
balanceOf
返回地址是_owner
的賬戶的賬戶余額。
function balanceOf(address _owner) constant returns (uint256 balance)
transfer
轉移_value
的token數量到的地址_to
,并且必須觸發Transfer
事件。 如果_from
帳戶余額沒有足夠的令牌來支出,該函數應該被throw
。
創建新令牌的令牌合同應該在創建令牌時將_from
地址設置為0x0
觸發傳輸事件。
注意 0值的傳輸必須被視為正常傳輸并觸發傳輸事件。
function transfer(address _to, uint256 _value) returns (bool success)
transferFrom
從地址_from
發送數量為_value
的token到地址_to
,必須觸發Transfer
事件。
transferFrom方法用于提取工作流,允許合同代您轉移token。這可以用于例如允許合約代您轉讓代幣和/或以子貨幣收取費用。除了_from帳戶已經通過某種機制故意地授權消息的發送者之外,該函數應該throw。
注意 0值的傳輸必須被視為正常傳輸并觸發傳輸事件。
function transferFrom(address _from, address _to, uint256 _value) returns (bool success)
approve
允許_spender
多次取回您的帳戶,最高達_value
金額。 如果再次調用此函數,它將以_value
覆蓋當前的余量。
注意:為了阻止向量攻擊,客戶端需要確認以這樣的方式創建用戶接口,即將它們設置為0,然后將其設置為同一個花費者的另一個值。雖然合同本身不應該強制執行,允許向后兼容以前部署的合同兼容性
function approve(address _spender, uint256 _value) returns (bool success)
allowance
返回_spender
仍然被允許從_owner
提取的金額。
function allowance(address _owner, address _spender) constant returns (uint256 remaining)
Events
Transfer
當token被轉移(包括0值),必須被觸發。
event Transfer(address indexed _from, address indexed _to, uint256 _value)
Approval
當任何成功調用approve(address _spender, uint256 _value)
后,必須被觸發。
event Approval(address indexed _owner, address indexed _spender, uint256 _value)
[官網接口說明點擊查看],(https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md)
接口文件ERC20Interface.sol
如下:
contract ERC20Interface {
string public constant name = "Token Name";
string public constant symbol = "SYM";
uint8 public constant decimals = 18; // 18 is the most common number of decimal places
// 0.0000000000000000001 個代幣
function totalSupply() public constant returns (uint);
function balanceOf(address tokenOwner) public constant returns (uint balance);
function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
function approve(address spender, uint tokens) public returns (bool success);
function transfer(address to, uint tokens) public returns (bool success);
function transferFrom(address from, address to, uint tokens) public returns (bool success);
event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}
3. TOKEN合約代碼
合約文件TokenERC20.sol
如下:
pragma solidity ^0.4.16;
interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }
contract TokenERC20 {
string public name;
string public symbol;
uint8 public decimals = 18; // decimals 可以有的小數點個數,最小的代幣單位。18 是建議的默認值
uint256 public totalSupply;
// 用mapping保存每個地址對應的余額
mapping (address => uint256) public balanceOf;
// 存儲對賬號的控制
mapping (address => mapping (address => uint256)) public allowance;
// 事件,用來通知客戶端交易發生
event Transfer(address indexed from, address indexed to, uint256 value);
// 事件,用來通知客戶端代幣被消費
event Burn(address indexed from, uint256 value);
/**
* 初始化構造
*/
function TokenERC20(uint256 initialSupply, string tokenName, string tokenSymbol) public {
totalSupply = initialSupply * 10 ** uint256(decimals); // 供應的份額,份額跟最小的代幣單位有關,份額 = 幣數 * 10 ** decimals。
balanceOf[msg.sender] = totalSupply; // 創建者擁有所有的代幣
name = tokenName; // 代幣名稱
symbol = tokenSymbol; // 代幣符號
}
/**
* 代幣交易轉移的內部實現
*/
function _transfer(address _from, address _to, uint _value) internal {
// 確保目標地址不為0x0,因為0x0地址代表銷毀
require(_to != 0x0);
// 檢查發送者余額
require(balanceOf[_from] >= _value);
// 確保轉移為正數個
require(balanceOf[_to] + _value > balanceOf[_to]);
// 以下用來檢查交易,
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);
// 用assert來檢查代碼邏輯。
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}
/**
* 代幣交易轉移
* 從創建交易者賬號發送`_value`個代幣到 `_to`賬號
*
* @param _to 接收者地址
* @param _value 轉移數額
*/
function transfer(address _to, uint256 _value) public {
_transfer(msg.sender, _to, _value);
}
/**
* 賬號之間代幣交易轉移
* @param _from 發送者地址
* @param _to 接收者地址
* @param _value 轉移數額
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
require(_value <= allowance[_from][msg.sender]); // Check allowance
allowance[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}
/**
* 設置某個地址(合約)可以交易者名義花費的代幣數。
*
* 允許發送者`_spender` 花費不多于 `_value` 個代幣
*
* @param _spender The address authorized to spend
* @param _value the max amount they can spend
*/
function approve(address _spender, uint256 _value) public
returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}
/**
* 設置允許一個地址(合約)以交易者名義可最多花費的代幣數。
*
* @param _spender 被授權的地址(合約)
* @param _value 最大可花費代幣數
* @param _extraData 發送給合約的附加數據
*/
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) 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;
}
/**
* 銷毀用戶賬戶中指定個代幣
*
* Remove `_value` tokens from the system irreversibly on behalf of `_from`.
*
* @param _from the address of the sender
* @param _value the amount of money to burn
*/
function burnFrom(address _from, uint256 _value) 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;
}
}
函數的功能參考函數的說明描述和代碼自解釋。
4. 合約編譯部署和發布
MetaMask錢包聯網
【前置條件】作者假設學習者已完成MetaMask的安裝和配置。還沒有完成的,參考《第六課 技術小白如何開發一個DAPP區塊鏈應用(以寵物商店為例)》的章節“5. 安裝 MetaMask和配置區塊鏈網絡”,在本地WIDOWS環境完成MetaMask輕錢包客戶端的安裝和配置。
打開MetaMask錢包,點擊左上角的“Ropsten Test Network” 連接成功。查看存量的賬號Account 1,其中ETH余額顯示為0。
點擊“Copy Address to Clipboard”,記錄Account 1錢包地址為
點擊MetaMask右上角環形頭像的菜單“Create Account”, 創建一個新錢包賬號Account 8,0xD1F7922e8b78cBEB182250753ade8379d1E09949
記錄Account 8的錢包地址為
0x3D7DfB80E71096F2c4Ee63C42C4D849F2CBBE363
點擊Account 8的"BUY"按鈕,可以從“Ropsten Test Network” 免費獲取一些測試ETH。
點擊按鈕“ROPSTEN TEST FAUCET”可以看到贈送頁面
點擊綠色按鈕,等待10秒以上,成功的話每次可以獲取1個測試ETH。
【故障排查】
作者操作時,出現錯誤。從提示看,是由于用戶交易拒絕。等10秒后再點擊該綠色按鈕則未有錯誤提示了。原因不明,可能是操作頻繁導致。
多次點擊,偶爾出錯,小編一共從這個測試網站獲取了5個測試ETH用于作為發幣的GAS燃料。
Remix Solidity IDE調試環境介紹
1,代碼編寫和編譯
我們以第二課的“Hello World”智能合約為例,參考下圖可完成編譯和語法錯誤發現。
2,合約創建
參考附圖描述,在配置號MetaMask賬號和網絡連接的情況下,確保賬戶有虛擬ETH用于合約創建花銷。最后點擊“Create”按鈕可以完成合約創建。點擊輸出區的網頁鏈接可以查看合約的詳細情況。
3, 合約執行
參考附件路徑圖,點擊RUN按鈕可以執行一次合約。點擊下方的“Say”按鈕,可以看到合約輸出。在調試輸出窗口可以看到“call to hello.say”的運行提示。
【總結】所以說,沒有Ubuntu+Ganache等,直接在WINDOWS環境,也可以使用Remix+MetaMask+Ropsten Test Network組合完成一套完整的以太坊測試環境。
更詳細的REMIX幫助文檔參考第十課 Solidity語言編輯器REMIX指導大全
編譯ERC20智能合約
CHROME瀏覽器打開Remix Solidity IDE環境,打開之前編寫的“TokenERC20.sol”智能合約,然后點擊右側的“start to compile”按鈕。可以看到,除了一些Warning提示外,智能合約編譯成功。
運行ERC20智能合約
切換到"RUN"頁面,Environment選擇“Injected Web3”, Account自動更新為MetaMask的Account 8賬號。“Create”按鈕前按照發幣數量(本次發1個億),代幣名稱,代幣符號填寫為100000000,"ColorBay","CB"。
點擊"Create"按鈕,會彈出一個交易確認框,設置合理的Gas Price,但要確保Max Transaction Fee不會超過Account 8的ETH總數,點“SUBMIT”按鈕。
部署成功的話,Account8 會產生一條交易記錄,顯示狀態為“Contract Published”,表示部署成功了。
【說明】有時點擊賬號,會出現“Retry with a higher gas price here”的提示,一般情況下再等待10秒看看能否交易成功。萬一還是不成的話,可考慮該Gas Price大點。
MetaMask加載TOKEN
點擊Account 8的交易記錄,可以跳轉智能合約部署信息顯示頁面:
獲取智能合約地址為0x5eeec41dc08d7caece17c4a349635934637036f1,點擊可查看該交易詳情。
點擊MetaMask的Account 8賬號的TOKENS頁面的“ADD TOKEN”按鈕,把代幣智能合約地址Token Contract Address為0x5eeec41dc08d7caece17c4a349635934637036f1,代幣標識符Token Symbol為CB,則可以創建你的代幣了。
代幣創建成功,一共有1億個CB幣了。此時,點擊100000000的代幣位置,跳轉到代幣查詢頁面, 可以在etherscan網站看到賬戶的持幣數量了。
截止作者發稿時,以太坊的價格為2559元/個,而你有1億個CB幣,是不是快要走上人生巔峰了呢?哈哈,做個夢而已。真正的區塊鏈應用,要能有生態,能為人類社會創造價值,而不是講故事,割韭菜!
5. 代幣交易
由于MetaMask插件沒有提供代幣交易功能,同時考慮到很多人并沒有以太坊錢包或是被以太坊錢包網絡同步問題折磨,今天我用網頁錢包來講解代幣交易。
1. 進入網頁錢包地址
第一次進入有一些安全提示需要用戶確認。
2. 進入之后,按照下圖進行設置
3,增加自定義代幣
填寫地址Token Contract 為0x5eeec41dc08d7caece17c4a349635934637036f1和其他信息,點擊保存按鈕。
配置成功的界面如下:
【說明】如果不成功,則提示為“Not a valid ERC-20 token CB”,則可能是代幣信息填寫不正確或者右上角的網絡選擇錯誤,沒有選擇“Network Ropston(infura.com)”選項引起。
4.轉賬給Account 1
填寫Account 1的地址“0xD1F7922e8b78cBEB182250753ade8379d1E09949”到“發送至地址”輸入框,
查看賬戶余額,Account 8減少800萬個CB幣,而Account 1則增加了800萬個CB幣。
轉賬交易成功了!
作為一個古典投資人,用45分鐘完成了TOKEN上線和交易,用4個小時整理了這篇文章。學習就是這么簡單,只要你對新技術保持饑餓感,高齡開發轉型區塊鏈也不是難事!
6. 商用實施
做一個ERC20通證玩玩是比較簡單的。但是如果要做一個需要發布并廣泛使用的通證,防止出現美鏈那樣60多億的價值一夜置零的安全問題,那還是要注意安全性問題。我們商用發布的ERC20通證跟上面DEMO的比起來,增加了10來位CTO的同行評審,取得了鏈安科技的專業安全審計報告,然后才發布出來。如果各位要交流商用技術,請加群交流。
輝哥和歐陽哥哥在知識星球開通了區塊鏈入門專欄,用于存放簡書區塊鏈入門專欄文章的工程源碼等內容,并建立專項微信群用于技術交流,歡迎加入。