在以太坊上編寫自己的加密貨幣(如何構建ERC-20令牌和銷售網站)
今天我將向您展示如何在以太坊區塊鏈上編碼您自己的加密貨幣並將其出售我將向您展示如何使用以太坊智能合約逐步創建自己的ERC-20令牌和眾籌銷售,如何測試智能合約,如何將智能合約部署到以太坊區塊鏈,以及如何構建ICO網站部署到網路上。我還將解釋ERC-20令牌是什麼,以太坊令牌如何工作,初始硬幣產品(ICO)如何工作。
什麼是ERC-20令牌?
以太坊區塊鏈允許您創建自己的加密貨幣或令牌,可以通過以太幣(以太坊區塊鏈的本機加密貨幣)購買。ERC-20隻是一個標準,它指定了這些令牌的行為方式,因此它們與加密貨幣交易所等其他平台兼容。
那怎麼辦?那麼,我們先來看看以太坊區塊鏈的工作原理。
以太坊是像比特幣一樣的區塊鏈。與比特幣一樣,以太坊也會跟蹤擁有以太坊,以太坊的原生加密的人的賬戶餘額。與比特幣不同,以太坊也是一個平台,允許您創建自己的令牌而無需創建新的區塊鏈。
您可以使用智能合約創建以太坊令牌。ERC-20是一種標準,用於指定此令牌智能合約應如何工作。
讓我們用一個例子來理解ERC-20令牌智能合約的工作原理。假設我們想創建一個名為「My代幣」的標記,其符號為「MTK」,並且存在100,000,000個這樣的標記。
首先,令牌智能合約跟蹤一些基本令牌屬性。例如,它記錄名稱「My代幣」,您在加密貨幣交易所中看到的符號,以及存在多少總令牌。
它還跟蹤誰擁有「我的令牌」和多少。
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
事件。請注意,智能合約可以發出消費者可以訂閱的事件,並且使用此標準,我們可以訂閱告訴我們何時銷售代幣的事件。
以下是transfer
ERC-20標準指定的功能的示例實現。這是智能合約所要求的,並且管理某人如何將錢包中的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
。
如果這一切還沒有完全有意義,請不要擔心。我將在逐步視頻教程中構建ERC-20令牌時詳細解釋所有這些部分。
您可以直接在以太坊改進提案 github存儲庫中閱讀有關ERC-20令牌標準的更多信息。這是圍繞以太坊標準進行的所有社區討論的地方。我強烈建議將該存儲庫加入書籤並閱讀提交內容,因為這是您可以觀看以太坊技術實時增長和變化的地方
我也推薦這篇維基百科的文章。
這就是我們要建立的
我們將建立一個ICO網站,與區塊鏈上的人群銷售智能合約進行對話。這個客戶端網站將有一個表格,用戶可以在人群銷售中購買令牌。它將顯示人群銷售的進度,例如用戶購買了多少令牌,所有用戶購買了多少令牌,以及人群銷售中可用的令牌總數。它還會在「您的帳戶」下顯示我們與區塊鏈關聯的帳戶。
安裝依賴項
為了構建我們的ERC-20令牌和眾籌銷售,我們首先需要一些依賴。
節點包管理器(NPM)
我們需要的第一個依賴是Node Package Manager,或NPM,它隨Node.js一起提供。您可以通過轉到您的期限並輸入以下內容來查看是否已安裝節點:
$node -v
Truffle框架
下一個依賴是Truffle Framework,它允許我們在以太坊區塊鏈上構建去中心化的應用程序。它提供了一套工具,允許我們使用Solidity編程語言編寫智能聯繫人。它還使我們能夠測試我們的智能合約並將其部署到區塊鏈。它還為我們提供了開發客戶端應用程序的空間。
您可以在命令行中使用NPM安裝Truffle,如下所示:
$npm install -g truffle
伽納徹
下一個依賴項是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令牌智能合約
本部分教程的隨附視頻片段以此視頻開頭。您也可以下載代碼從github上智能本合約在這裡。
現在我們已經安裝了依賴項,讓我們開始構建我們的ERC-20令牌這是完整的ERC-20令牌智能合約Solidity代碼:
pragma solidity ^0.4.2;
contract DappToken {
string public name = "DApp代幣";
string public symbol = "DAPP";
string public standard = "DApp代幣 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代幣"
。 - 它存儲用於加密貨幣交易所的令牌符號
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代幣', '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代幣 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
人群銷售智能合約
本部分教程的隨附視頻片段以此視頻開頭。您也可以下載代碼從github上智能本合約在這裡。
現在我們可以建立一個人群銷售智能合約,允許投資者在最初的硬幣發行(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
。 - 它引用了ERC-20令牌智能合約
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令牌和眾包銷售智能合約您可以在這裡查看完整的8小時視頻教程,了解如何建立一個與智能合約對話並促進代幣購買的ICO網站。它還包括用於構建智能合約本身的深入分步說明。您也可以從github 這裡下載完整的源代碼到本教程。
原文:https://www.dappuniversity.com/articles/code-your-own-cryptocurrency-on-ethereum