在以太坊開發自己的加密貨幣(建立ERC-20代幣和如何ICO)
今天我將向你展示如何在以太坊區塊鏈上開發你自己的加密貨幣並將其出售!我將向你展示如何使用以太坊智慧合約逐步建立自己的ERC-20代幣和眾籌銷售,如何測試智慧合約,如何將智慧合約部署到以太坊區塊鏈,以及如何構建ICO網站部署到網路上。我還將解釋ERC-20代幣是什麼,以太坊代幣如何工作,初始代幣產品(ICO)如何工作。
什麼是ERC-20代幣?
以太坊區塊鏈允許你建立自己的加密貨幣或代幣,可以通過以太幣(以太坊區塊鏈的本地加密貨幣)購買。ERC-20只是一個標準,它指定了這些代幣的行為方式,因此它們與加密貨幣交換等其他平臺相容。
那怎麼做呢?讓我們先來看看以太坊區塊鏈的工作原理。
以太坊是像比特幣一樣的區塊鏈。與比特幣一樣,以太坊也會跟蹤擁有Ether的使用者餘額,以太坊的原生加密貨幣。與比特幣不同,以太坊也是一個平臺,允許你建立自己的代幣而無需建立新的區塊鏈。
你可以使用智慧合約建立以太坊代幣。ERC-20是一個標準,用於指定此代幣智慧合約應如何工作。
讓我們用一個例子來理解ERC-20代幣智慧合約的工作原理。假設我們想要建立一個名為“My Token”的代幣,其符號為“MTK”,並且存在100,000,000個這樣的代幣。
首先,代幣智慧合約跟蹤一些基本代幣屬性。例如,它記錄名稱“My Token”,你在加密貨幣交換中看到的符號,以及存在多少總代幣。
它還跟蹤誰擁有“My Token”和多少。
ERC-20代幣可以作為付款從一個帳戶轉移到另一個帳戶,就像任何其他加密貨幣一樣。
它們也可以在眾籌銷售中購買,如ICO,我們將在下一節中進行討論。
它們也可以在加密貨幣交易所買賣。
ICO如何運作
ERC-20代幣可以以多種方式分發。一種流行的方法是舉行目標人群促銷或初始代幣發行(ICO)。眾籌銷售是公司通過建立自己的ERC-20代幣來為其業務籌集資金的一種方式,該代幣可以由以太幣的投資者購買。
每當發生眾籌銷售時,公司就會以投資者支付的以太幣形式獲得流動資金,並持有在眾籌銷售中出售的預留金額的ERC-20代幣。
為了參與眾籌銷售,投資者必須使用帳戶連線到Etherum區塊鏈。此帳戶有一個可以儲存以太幣的錢包地址,以及在眾籌銷售中購買的ERC-20代幣。
投資者必須訪問與智慧合約談話的眾籌銷售網站。智慧合約管理眾籌銷售如何運作的所有規則。
每當投資者在眾籌銷售網站上購買代幣時,他們就會將以太幣從他們的錢包傳送到智慧合約,而智慧合約會立即將購買的代幣分發到他們的錢包中。
智慧合約在眾籌銷售中設定代幣的價格並控制眾籌銷售的行為方式。
眾籌銷售可以採取各種形狀和大小。它們可以具有多個層級或階段,例如Pre ICO,ICO和ICO Bonus階段。這些層中的每一層都可以在不同的時間點發生並且可以表現不同。
他們還可以使用白名單來限制哪些投資者可以購買代幣。
他們還可以擁有預定數量的代幣,這些代幣不會在眾籌銷售中出售。這些儲備通常留給每個公司的特定成員,如創始人和顧問。這些儲備可以是固定數量的代幣或百分比。
每當眾籌銷售結束時,它可以由管理員最終確定。每當發生這種情況時,所有預留的代幣都將分發到相應的帳戶,眾籌銷售將正式結束。
ERC-20代幣的工作原理
正如我之前解釋的那樣,ERC-20代幣是使用以太坊智慧合約建立的。什麼是智慧合約?
以太坊允許開發人員使用智慧合約編寫在區塊鏈上執行的應用程式,這些應用程式封裝了這些應用程式的所有業務邏輯。它們使我們能夠讀取和寫入區塊鏈的資料,以及執行程式碼。智慧合約使用名為Solidity的程式語言編寫,看起來很像Javascript。它是一種完整的程式語言,它允許我們執行Javascript所能提供的許多相同型別的事情,但由於它的用例,它的行為有點不同,我們將在本教程中看到。
對於ERC-20代幣,智慧合約管理有關代幣如何工作的所有行為,並跟蹤代幣所有權和帳戶餘額。
ERC-20是關於如何構建以太坊代幣的API規範。它是一種社群採用的標準,允許在各種用例中支援代幣。我們希望構建一個符合此標準的代幣,以便廣泛接受。如果我們沒有這樣的標準,我們可以有無盡的方式來建立代幣,它們可能彼此不相容!
使用ERC-20標準可確保代幣符合以下用例(以及更多):
- 電子錢包轉帳 - 將代幣從一個帳戶傳送到另一個帳戶
- 在加密貨幣交易所買賣
- 在眾籌銷售(ICO)中購買代幣,就像我們將在本教程中演示一樣
ERC-20規範基本上規定了智慧合約必須響應的介面。它規定了智慧合約的結構和智慧合約必須具備的功能型別。它還提供了一些很好的建議功能,但最終是可選的。它規定了我們的代幣必須具有的某些事件,例如 transfer
事件。請注意,智慧合約可以發出消費者可以訂閱的事件,並且使用此標準,我們可以訂閱告訴我們何時銷售代幣的事件。
以下是ERC-20標準指定的 transfer
函式的示例實現。它是智慧合約所要求的,並且管理某人如何將錢包中的ERC-20代幣傳送到另一個錢包。
contract ERC20Token { // ... function transfer(address _to, uint256 _value) public returns (bool success) { require(balanceOf[msg.sender] >= _value); balanceOf[msg.sender] -= _value; balanceOf[_to] += _value; Transfer(msg.sender, _to, _value); return true; } // ... }
該函式通過以下方式實現ERC-20標準:
sell true
如果所有這些還沒有完全有意義,請不要擔心。你可以直接在 ofollow,noindex">以太坊改進提案 github儲存庫中閱讀有關 ERC-20令牌標準 的更多資訊。這是圍繞以太坊標準進行的所有社群討論的地方。我強烈建議將該儲存庫加入書籤並閱讀提交內容,因為這是你可以觀看以太坊技術實時增長和變化的地方!
我也推薦這篇 維基百科文章 。
我們要建立的網站
我們將建立一個ICO網站,與區塊鏈上的眾籌銷售智慧合約進行對話。這個客戶端網站將有一個表格,使用者可以在眾籌銷售中購買令牌。它將顯示眾籌銷售的進度,例如使用者購買了多少令牌,所有使用者購買了多少令牌,以及眾籌銷售中可用的令牌總數。它還會在“你的帳戶”下顯示我們與區塊鏈相關聯的帳戶。
安裝依賴項
為了構建我們的ERC-20令牌和銷售,我們首先需要一些依賴。
節點包管理器(NPM)
我們需要的第一個依賴是節點包管理器,或NPM,它與Node.js一起提供。你可以通過你的終端並輸入以下內容來檢視你是否已安裝節點:
$ node -v
Truffle框架
下一個依賴是Truffle Framework,它允許我們在以太坊區塊鏈上構建去中心化的應用程式。它提供了一套工具,允許我們使用Solidity程式語言編寫智慧合約。它還使我們能夠測試我們的智慧合約並將其部署到區塊鏈。它還為我們提供了開發客戶端應用程式的地方。
你可以在命令列中使用NPM安裝Truffle,如下所示:
$ npm install -g truffle
Ganache
下一個依賴項是Ganache,一個本地記憶體區塊鏈。你可以通過從Truffle Framework網站下載來安裝Ganache。它將為我們提供10個外部帳戶,其中包含我們當地以太坊區塊鏈的地址。每個帳戶預裝100個測試Ether。
Metamask
下一個依賴項是Google Chrome的Metamask擴充套件。為了使用區塊鏈,我們必須連線它(記住,我說塊鏈是一個網路)。我們必須安裝一個特殊的瀏覽器擴展才能使用以太坊區塊鏈。這就是Metamask的用武之地。我們將能夠通過我們的個人賬戶連線到我們當地的以太坊區塊鏈,並與我們的智慧合約進行互動。
我們將在本教程中使用Metamask chrome擴充套件,因此如果你還沒有安裝google chrome瀏覽器,則還需要安裝它。要安裝Metamask,請在Google Chrome網上應用店中搜索Metamask Chrome外掛。安裝完成後,請確保在擴充套件列表中選中它。安裝後,你會在Chrome瀏覽器的右上角看到狐狸圖示。
語法高亮顯示
依賴項是可選的,但建議使用。我建議為Solidity程式語言安裝語法高亮顯示。大多數文字編輯器和IDE都沒有開箱即用的Solidity語法高亮顯示,因此你必須安裝一個軟體包才能支援此功能。我正在使用Sublime Text,我已經下載了“Ethereum”軟體包,它為Solidity提供了很好的語法高亮顯示。
ERC-20令牌智慧合約
現在我們已經安裝了依賴項,讓我們開始構建我們的ERC-20令牌!這是完整的ERC-20令牌智慧合約Solidity程式碼:
pragma solidity ^0.4.2; contract DappToken { stringpublic name = "DApp Token"; stringpublic symbol = "DAPP"; stringpublic standard = "DApp Token v1.0"; uint256 public totalSupply; event Transfer( address indexed _from, address indexed _to, uint256 _value ); event Approval( address indexed _owner, address indexed _spender, uint256 _value ); mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; function DappToken (uint256 _initialSupply) public { balanceOf[msg.sender] = _initialSupply; totalSupply = _initialSupply; } function transfer(address _to, uint256 _value) public returns (bool success) { require(balanceOf[msg.sender] >= _value); balanceOf[msg.sender] -= _value; balanceOf[_to] += _value; Transfer(msg.sender, _to, _value); return true; } function approve(address _spender, uint256 _value) public returns (bool success) { allowance[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); return true; } function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { require(_value <= balanceOf[_from]); require(_value <= allowance[_from][msg.sender]); balanceOf[_from] -= _value; balanceOf[_to] += _value; allowance[_from][msg.sender] -= _value; Transfer(_from, _to, _value); return true; } }
讓我們來看看這個智慧合約的功能,以及它如何實現ERC-20標準:
- 它儲存代幣名稱
string public name =“DApp Token”。
- 它儲存用於加密貨幣交換的代幣符號
string public symbol =“DAPP”
。 - 它儲存了
uint256 public totalSupply
公共代幣總供應量。 - 它使用Solidity對映來儲存擁有代幣對映
mapping(address => uint256) public balanceOf
的每個帳戶的餘額。 - 它實現了一個
transfer
函式,允許使用者將代幣傳送到另一個帳戶。 - 它實現了一個允許其他帳戶使用代幣的
approve
函式,例如加密貨幣交換。這會更新allowance
對映,以檢視帳戶可以支出的金額。 - 它實現了
transferFrom
,允許其他帳戶轉移令牌。
你還可以閱讀此智慧合約的測試,以瞭解有關其工作原理的更多資訊。這些測試確保這種智慧合約的行為符合我們的預期。這是一個完整的測試套件,可以檢查智慧合約的所有行為:
var DappToken = artifacts.require("./DappToken.sol"); contract('DappToken', function(accounts) { var tokenInstance; it('initializes the contract with the correct values', function() { return DappToken.deployed().then(function(instance) { tokenInstance = instance; return tokenInstance.name(); }).then(function(name) { assert.equal(name, 'DApp Token', 'has the correct name'); return tokenInstance.symbol(); }).then(function(symbol) { assert.equal(symbol, 'DAPP', 'has the correct symbol'); return tokenInstance.standard(); }).then(function(standard) { assert.equal(standard, 'DApp Token v1.0', 'has the correct standard'); }); }) it('allocates the initial supply upon deployment', function() { return DappToken.deployed().then(function(instance) { tokenInstance = instance; return tokenInstance.totalSupply(); }).then(function(totalSupply) { assert.equal(totalSupply.toNumber(), 1000000, 'sets the total supply to 1,000,000'); return tokenInstance.balanceOf(accounts[0]); }).then(function(adminBalance) { assert.equal(adminBalance.toNumber(), 1000000, 'it allocates the initial supply to the admin account'); }); }); it('transfers token ownership', function() { return DappToken.deployed().then(function(instance) { tokenInstance = instance; // Test `require` statement first by transferring something larger than the sender's balance return tokenInstance.transfer.call(accounts[1], 99999999999999999999999); }).then(assert.fail).catch(function(error) { assert(error.message.indexOf('revert') >= 0, 'error message must contain revert'); return tokenInstance.transfer.call(accounts[1], 250000, { from: accounts[0] }); }).then(function(success) { assert.equal(success, true, 'it returns true'); return tokenInstance.transfer(accounts[1], 250000, { from: accounts[0] }); }).then(function(receipt) { assert.equal(receipt.logs.length, 1, 'triggers one event'); assert.equal(receipt.logs[0].event, 'Transfer', 'should be the "Transfer" event'); assert.equal(receipt.logs[0].args._from, accounts[0], 'logs the account the tokens are transferred from'); assert.equal(receipt.logs[0].args._to, accounts[1], 'logs the account the tokens are transferred to'); assert.equal(receipt.logs[0].args._value, 250000, 'logs the transfer amount'); return tokenInstance.balanceOf(accounts[1]); }).then(function(balance) { assert.equal(balance.toNumber(), 250000, 'adds the amount to the receiving account'); return tokenInstance.balanceOf(accounts[0]); }).then(function(balance) { assert.equal(balance.toNumber(), 750000, 'deducts the amount from the sending account'); }); }); it('approves tokens for delegated transfer', function() { return DappToken.deployed().then(function(instance) { tokenInstance = instance; return tokenInstance.approve.call(accounts[1], 100); }).then(function(success) { assert.equal(success, true, 'it returns true'); return tokenInstance.approve(accounts[1], 100, { from: accounts[0] }); }).then(function(receipt) { assert.equal(receipt.logs.length, 1, 'triggers one event'); assert.equal(receipt.logs[0].event, 'Approval', 'should be the "Approval" event'); assert.equal(receipt.logs[0].args._owner, accounts[0], 'logs the account the tokens are authorized by'); assert.equal(receipt.logs[0].args._spender, accounts[1], 'logs the account the tokens are authorized to'); assert.equal(receipt.logs[0].args._value, 100, 'logs the transfer amount'); return tokenInstance.allowance(accounts[0], accounts[1]); }).then(function(allowance) { assert.equal(allowance.toNumber(), 100, 'stores the allowance for delegated trasnfer'); }); }); it('handles delegated token transfers', function() { return DappToken.deployed().then(function(instance) { tokenInstance = instance; fromAccount = accounts[2]; toAccount = accounts[3]; spendingAccount = accounts[4]; // Transfer some tokens to fromAccount return tokenInstance.transfer(fromAccount, 100, { from: accounts[0] }); }).then(function(receipt) { // Approve spendingAccount to spend 10 tokens form fromAccount return tokenInstance.approve(spendingAccount, 10, { from: fromAccount }); }).then(function(receipt) { // Try transferring something larger than the sender's balance return tokenInstance.transferFrom(fromAccount, toAccount, 9999, { from: spendingAccount }); }).then(assert.fail).catch(function(error) { assert(error.message.indexOf('revert') >= 0, 'cannot transfer value larger than balance'); // Try transferring something larger than the approved amount return tokenInstance.transferFrom(fromAccount, toAccount, 20, { from: spendingAccount }); }).then(assert.fail).catch(function(error) { assert(error.message.indexOf('revert') >= 0, 'cannot transfer value larger than approved amount'); return tokenInstance.transferFrom.call(fromAccount, toAccount, 10, { from: spendingAccount }); }).then(function(success) { assert.equal(success, true); return tokenInstance.transferFrom(fromAccount, toAccount, 10, { from: spendingAccount }); }).then(function(receipt) { assert.equal(receipt.logs.length, 1, 'triggers one event'); assert.equal(receipt.logs[0].event, 'Transfer', 'should be the "Transfer" event'); assert.equal(receipt.logs[0].args._from, fromAccount, 'logs the account the tokens are transferred from'); assert.equal(receipt.logs[0].args._to, toAccount, 'logs the account the tokens are transferred to'); assert.equal(receipt.logs[0].args._value, 10, 'logs the transfer amount'); return tokenInstance.balanceOf(fromAccount); }).then(function(balance) { assert.equal(balance.toNumber(), 90, 'deducts the amount from the sending account'); return tokenInstance.balanceOf(toAccount); }).then(function(balance) { assert.equal(balance.toNumber(), 10, 'adds the amount from the receiving account'); return tokenInstance.allowance(fromAccount, spendingAccount); }).then(function(allowance) { assert.equal(allowance.toNumber(), 0, 'deducts the amount from the allowance'); }); }); });
你可以使用truffle從命令列執行測試,如下所示:
$ truffle test
眾籌銷售智慧合約
現在我們可以建立一個眾籌銷售智慧合約,允許投資者在最初的代幣產品(ICO)中購買代幣。這是完整的眾籌銷售智慧合約Solidity程式碼:
pragma solidity ^0.4.2; import "./DappToken.sol"; contract DappTokenSale { address admin; DappToken public tokenContract; uint256 public tokenPrice; uint256 public tokensSold; event Sell(address _buyer, uint256 _amount); function DappTokenSale(DappToken _tokenContract, uint256 _tokenPrice) public { admin = msg.sender; tokenContract = _tokenContract; tokenPrice = _tokenPrice; } function multiply(uint x, uint y) internal pure returns (uint z) { require(y == 0 || (z = x * y) / y == x); } function buyTokens(uint256 _numberOfTokens) public payable { require(msg.value == multiply(_numberOfTokens, tokenPrice)); require(tokenContract.balanceOf(this) >= _numberOfTokens); require(tokenContract.transfer(msg.sender, _numberOfTokens)); tokensSold += _numberOfTokens; Sell(msg.sender, _numberOfTokens); } function endSale() public { require(msg.sender == admin); require(tokenContract.transfer(admin, tokenContract.balanceOf(this))); // Just transfer the balance to the admin admin.transfer(address(this).balance); } }
讓我們來看看這個智慧合約的功能,以及它如何進行眾籌銷售:
address admin DappToken public tokenContract uint256 public tokenPrice uint256 public tokensSold sell buyTokens endSale
var DappToken = artifacts.require('./DappToken.sol'); var DappTokenSale = artifacts.require('./DappTokenSale.sol'); contract('DappTokenSale', function(accounts) { var tokenInstance; var tokenSaleInstance; var admin = accounts[0]; var buyer = accounts[1]; var tokenPrice = 1000000000000000; // in wei var tokensAvailable = 750000; var numberOfTokens; it('initializes the contract with the correct values', function() { return DappTokenSale.deployed().then(function(instance) { tokenSaleInstance = instance; return tokenSaleInstance.address }).then(function(address) { assert.notEqual(address, 0x0, 'has contract address'); return tokenSaleInstance.tokenContract(); }).then(function(address) { assert.notEqual(address, 0x0, 'has token contract address'); return tokenSaleInstance.tokenPrice(); }).then(function(price) { assert.equal(price, tokenPrice, 'token price is correct'); }); }); it('facilitates token buying', function() { return DappToken.deployed().then(function(instance) { // Grab token instance first tokenInstance = instance; return DappTokenSale.deployed(); }).then(function(instance) { // Then grab token sale instance tokenSaleInstance = instance; // Provision 75% of all tokens to the token sale return tokenInstance.transfer(tokenSaleInstance.address, tokensAvailable, { from: admin }) }).then(function(receipt) { numberOfTokens = 10; return tokenSaleInstance.buyTokens(numberOfTokens, { from: buyer, value: numberOfTokens * tokenPrice }) }).then(function(receipt) { assert.equal(receipt.logs.length, 1, 'triggers one event'); assert.equal(receipt.logs[0].event, 'Sell', 'should be the "Sell" event'); assert.equal(receipt.logs[0].args._buyer, buyer, 'logs the account that purchased the tokens'); assert.equal(receipt.logs[0].args._amount, numberOfTokens, 'logs the number of tokens purchased'); return tokenSaleInstance.tokensSold(); }).then(function(amount) { assert.equal(amount.toNumber(), numberOfTokens, 'increments the number of tokens sold'); return tokenInstance.balanceOf(buyer); }).then(function(balance) { assert.equal(balance.toNumber(), numberOfTokens); return tokenInstance.balanceOf(tokenSaleInstance.address); }).then(function(balance) { assert.equal(balance.toNumber(), tokensAvailable - numberOfTokens); // Try to buy tokens different from the ether value return tokenSaleInstance.buyTokens(numberOfTokens, { from: buyer, value: 1 }); }).then(assert.fail).catch(function(error) { assert(error.message.indexOf('revert') >= 0, 'msg.value must equal number of tokens in wei'); return tokenSaleInstance.buyTokens(800000, { from: buyer, value: numberOfTokens * tokenPrice }) }).then(assert.fail).catch(function(error) { assert(error.message.indexOf('revert') >= 0, 'cannot purchase more tokens than available'); }); }); it('ends token sale', function() { return DappToken.deployed().then(function(instance) { // Grab token instance first tokenInstance = instance; return DappTokenSale.deployed(); }).then(function(instance) { // Then grab token sale instance tokenSaleInstance = instance; // Try to end sale from account other than the admin return tokenSaleInstance.endSale({ from: buyer }); }).then(assert.fail).catch(function(error) { assert(error.message.indexOf('revert' >= 0, 'must be admin to end sale')); // End sale as admin return tokenSaleInstance.endSale({ from: admin }); }).then(function(receipt) { return tokenInstance.balanceOf(admin); }).then(function(balance) { assert.equal(balance.toNumber(), 999990, 'returns all unsold dapp tokens to admin'); // Check that the contract has no balance balance = web3.eth.getBalance(tokenSaleInstance.address) assert.equal(balance.toNumber(), 0); }); }); });
恭喜!你已成功學習瞭如何在以太坊上建立了ERC-20代幣和眾籌銷售智慧合約!
======================================================================
分享一些以太坊、EOS、比特幣等區塊鏈相關的互動式線上程式設計實戰教程:
- java以太坊開發教程,主要是針對java和android程式設計師進行區塊鏈以太坊開發的web3j詳解。
- python以太坊,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
- php以太坊,主要是介紹使用php進行智慧合約開發互動,進行賬號建立、交易、轉賬、代幣開發以及過濾器和交易等內容。
- 以太坊入門教程,主要介紹智慧合約與dapp應用開發,適合入門。
- 以太坊開發進階教程,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
- C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括賬戶管理、狀態與交易、智慧合約開發與互動、過濾器和交易等。
- EOS教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、賬戶與錢包、發行代幣、智慧合約開發與部署、使用程式碼與智慧合約互動等核心知識點,最後綜合運用各知識點完成一個便籤DApp的開發。
- java比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在Java程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是Java工程師不可多得的比特幣開發學習課程。
- php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在Php程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
- tendermint區塊鏈開發詳解 ,本課程適合希望使用tendermint進行區塊鏈開發的工程師,課程內容即包括tendermint應用開發模型中的核心概念,例如ABCI介面、默克爾樹、多版本狀態庫等,也包括代幣發行等豐富的實操程式碼,是go語言工程師快速入門區塊鏈開發的最佳選擇。
匯智網原創翻譯,轉載請標明出處。這裡是原文 在以太坊開發自己的加密貨幣