如何在以太坊上编写自己的加密货币(如何构建ERC-20令牌和代币销售网站)
今天我将向您展示如何在以太坊区块链上编码您自己的加密货币并将其出售我将向您展示如何使用以太坊智能合约逐步创建自己的ERC-20令牌和众筹销售,如何测试智能合约,如何将智能合约部署到以太坊区块链,以及如何构建ICO网站部署到网络上。我还将解释ERC-20令牌是什么,以太坊令牌如何工作,初始硬币产品(ICO)如何工作。
您可以从github 这里下载完整的源代码到本教程。在我们开始构建ERC-20令牌和众筹之前,我先回答几个问题。
什么是ERC-20令牌?
以太坊区块链允许您创建自己的加密货币或令牌,可以通过以太币(以太坊区块链的本机加密货币)购买。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
人群销售智能合约
现在我们可以建立一个人群销售智能合约,允许投资者在最初的硬币发行(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 这里下载完整的源代码到本教程。
原文:http://www.dappuniversity.com/articles/code-your-own-cryptocurrency-on-ethereum