第八課 技術小白如何在45分鐘內發行通證(TOKEN)並上線交易
1. 文章摘要
【本文目標】
通過逐步的指導和截圖舉證,一步步帶領一個技術小白完成一個數字貨幣(通證,代幣,TOKEN)的釋出演示和上線交易。
【環境前置條件】
參考《第六課 技術小白如何開發一個DAPP區塊鏈應用(以寵物商店為例)》,已在本地WIDOWS環境完成MetaMask輕錢包客戶端的安裝和配置;作者建議最好遵循從頭開始的課程學習順序。不過如果你想半途插入實操學習,問題也不大,遇到障礙時反向找對應文章的指導內容即可完成。
【技術收穫】
從本實踐中,你可以學習到:
ERC20 Token的定義和實踐
使用Remix Solidity IDE編寫智慧合約和編譯除錯
使用MetaMask完成錢包賬戶檢視
使用
【實操課程列表】
【說明】未列出的課程為知識普及的非實操類課程,所有區塊鏈文章參考“區塊鏈入口”專欄。
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)
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錢包地址為
0xD1F7922e8b78cBEB182250753ade8379d1E09949
點選MetaMask右上角環形頭像的選單“Create Account”, 建立一個新錢包賬號Account 8,
記錄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外掛沒有提供代幣交易功能,同時考慮到很多人並沒有以太坊錢包或是被以太坊錢包網路同步問題折磨,今天我用網頁錢包來講解代幣交易。 第一次進入有一些安全提示需要使用者確認。 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. 總結參考文件 本文是站在巨人的肩膀上的操作實踐,感謝以下文章作者的幫助:
知識對接服務: 輝哥和歐陽哥哥在知識星球開通了區塊鏈入門專欄,用於存放簡書區塊鏈入門專欄文章的工程原始碼等內容,並建立專項微信群用於技術交流,歡迎加入。