上一篇文章从区块链,以太坊的概念,到去中心化应用,再到语言,框架,最后列举了一些开源项目,相信你已经入门了以太坊开发。还没有看的可以先点击链接了解一下。
接下来我们一步步创建并发行自己的代币。以太坊生态系统中的代币可以代表任何可交易的商品,如硬币、金币、白条及游戏道具等。以太坊中所有的代币都遵循基本的规则和协议实现,所以我们的代币可以兼容以太坊钱包以及其他遵循同一规则的客户端及智能合约。
代币合约
标准的代币合约会相当复杂,先来个简单的压压惊。
pragma solidity ^0.4.16;
interface tokenRecipient {
function receiveApproval(
address _from,
uint256 _value,
address _token,
bytes _extraData) public;
}
contract TokenERC20 { // Public variables of the token
string public name;
string public symbol;
// 18 decimals is the strongly suggested default, avoid changing it
uint8 public decimals = 18;
uint256 public totalSupply;
// This creates an array with all balances
mapping (address => uint256) public balanceOf;
// This generates a public event on the blockchain that will notify clients
mapping (address => mapping (address => uint256)) public allowance;
event Transfer(address indexed from, address indexed to, uint256 value);
// This notifies clients about the amount burnt
event Burn(address indexed from, uint256 value);
/**
* Constructor function
*
* Initializes contract with initial supply tokens to the creator of the contract
*/
function TokenERC20(
uint256 initialSupply,
string tokenName,
string tokenSymbol
) public {
// Update total supply with the decimal amount
totalSupply = initialSupply * 10 ** uint256(decimals);
balanceOf[msg.sender] = totalSupply;
name = tokenName;
symbol = tokenSymbol;
}
/**
* Internal transfer, only can be called by this contract
*/
function _transfer(address _from, address _to, uint _value) internal {
// Prevent transfer to 0x0 address. Use burn() instead
require(_to != 0x0);
// Check if the sender has enough
require(balanceOf[_from] >= _value);
// Check for overflows
require(balanceOf[_to] + _value > balanceOf[_to]);
// Save this for an assertion in the future
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);
// Asserts are used to use static analysis to find bugs in your code. They should never fail
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}
/**
* Transfer tokens
*
* Send `_value` tokens to `_to` from your account
*
* @param _to The address of the recipient
* @param _value the amount to send
*/
function transfer(address _to, uint256 _value) public {
_transfer(msg.sender, _to, _value);
}
/**
* Transfer tokens from other address
*
* Send `_value` tokens to `_to` on behalf of `_from`
*
* @param _from The address of the sender
* @param _to The address of the recipient
* @param _value the amount to send
*/
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;
}
/**
* Set allowance for other address
*
* Allows `_spender` to spend no more than `_value` tokens on your behalf
*
* @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;
}
/**
* Set allowance for other address and notify
*
* Allows `_spender` to spend no more than `_value` tokens on your behalf, and then ping the contract about it
*
* @param _spender The address authorized to spend
* @param _value the max amount they can spend
* @param _extraData some extra information to send to the approved contract
*/
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;
}
}
/**
* Destroy tokens
*
* Remove `_value` tokens from the system irreversibly
*
* @param _value the amount of money to burn
*/
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;
}
/**
* Destroy tokens from other account
*
* 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;
}
}
还是被吓到了,哈哈~我一开始也被吓到了。
来个更基本的:
contract MyToken {
/* This creates an array with all balances */
mapping (address => uint256) public balanceOf;
/* Initializes contract with initial supply tokens to the creator of the contract */
function MyToken(uint256 initialSupply) {
balanceOf[msg.sender] = initialSupply;
// Give the creator all initial tokens
}
/* Send coins */
function transfer(address _to, uint256 _value) {
require(balanceOf[msg.sender] >= _value);
// Check if the sender has enough
require(balanceOf[_to] + _value >= balanceOf[_to]);
// Check for overflows
balanceOf[msg.sender] -= _value;
// Subtract from the sender
balanceOf[_to] += _value;
// Add the same to the recipient
}
}
这个合约相当简单,一个状态变量用于存储所有账户的余额,并提供一个转账函数transfer()。
接下来我们一步步编写MyToken的代码。先自行去以太坊官网下载以太坊钱包,安装钱包,选择 rinkeby testnet。进入钱包后首先选择wallet,下面有 add account,然后输入密码就直接添加好了。
点击contract标签来到contract界面,然后点击Deploy New Contract部署新合约。
在Solidity Contract Source code编辑框中输入如下代码:
contract MyToken {
/* This creates an array with all balances */
mapping (address => uint256) public balanceOf;
}
这里声明了一个mapping类型的变量balanceOf,mapping类型可以理解为Java中的HashMap。public关键字,表示变量可以被区块链上的所有节点访问。Solidity会自动为public类型的变量生成getter方法。
这个合约目前还没什么用,由于我们并未生成任何代币,所以,如果有合约尝试获取我们代币的数额,得到的结果都是0。我们在合约部署的时候创建一些代币,在代码最后添加如下代码:
function MyToken() {
balanceOf[msg.sender] = 21000000;
}
函数与合约同名,作用相当于C语言中的构造函数,只有在合约第一次部署的时候才会被执行。部署合约后,合约创建者msg.sender将拥有21000000个代币。这里我们把数值写死在代码了,更好的方式是给构造函数提供一个参数,让合约创建者自己填,代码如下:
function MyToken(uint256 initialSupply) public {
balanceOf[msg.sender] = initialSupply;
}
看看钱包右边,点击下拉列表选择MyToken,你将可以看到一个编辑框让你输入构造函数的参数。
接下来为我们的代币添加转账功能。
/* Send coins */
function transfer(address _to, uint256 _value) { /* Add and subtract new balances */
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
}
当函数被调用时,调用者msg.sender将直接减去对应数量的代币,接收者得到相应数量的代币。这里有两个问题:
-
如果调用者尝试发送超过自己余额的代币,我们的合约将出现负数;
-
如果_value值过大,接收者的余额也可能溢出。
为了避免这两种情况,按照以前的经验,我们可以判断条件不满足时return或者throw抛出异常,反正以后都可以发布新版本升级。但合约不一样,合约一经部署将不可更改,也无法下线。而且throw会白白浪费掉调用者的gas。基于以上两点考虑,我们应该在合约部署前检查所有的数据,保证不会发生异常。Solidity提供了require()方法,我们修改代码如下:
function transfer(address _to, uint256 _value) {
/* Check if sender has balance and for overflows */
require(balanceOf[msg.sender] >= _value && balanceOf[_to] + _value >= balanceOf[_to]);
balanceOf[_to] += _value;
}
现在转账功能可以用了。但我们的代币还缺少一个名字和符号,直接在代码中声明相应的状态变量:
string public name;
string public symbol;
uint8 public decimals;
在构造函数中为变量赋值,这样在部署时我们可以灵活为自己的代币命名。
/* Initializes contract with initial supply tokens to the creator of the contract */
function MyToken(uint256 initialSupply, string tokenName, string tokenSymbol, uint8 decimalUnits) {
balanceOf[msg.sender] = initialSupply; // Give the creator all initial tokens
name = tokenName; // Set the name for display purposes
symbol = tokenSymbol; // Set the symbol for display purposes
decimals = decimalUnits; // Amount of decimals for display purposes
}
为了让钱包等客户端能够收到代币的通知,我们最后为代币添加Events事件。事件是使用EVM日志内置功能的方便工具,在DAPP的接口中,它可以反过来调用Javascript的监听事件的回调。event声明:
event Transfer(address indexed from, address indexed to, uint256 value);
在transfer()函数中调用事件:
/* Notify anyone listening that this transfer took place */
Transfer(msg.sender, _to, _value);
到这里,MyToken代币就开发完成了。
部署代币
填写构造函数需要的参数,你的钱包界面应该大致如下:
拉到底部,设置你愿意为代币部署花费的以太币数量,这里可以保留默认或者设置多一点,因为剩余的以太币会返还给我们,以太币设置少的话,将需要等待更多的时间完成部署。
点击Deploy。
输入密码即可提交部署。部署完成后可以体验下自己的代币,比如转发代币给自己的朋友。点击WALLET标签旁边的SEND标签,输入朋友的钱包地址,代币数量,选择自己的代币(默认是以太币),点击发送即可。
不过现在你朋友的钱包还无法看到你发送给他的代币,因为以太坊钱包只会跟踪它知道的货币,我们需要手动把自己刚创建的代币添加到钱包中。
先到自己刚创建的合约界面复制合约地址,点击Watch Token,在弹出的对话框中粘贴合约地址,名称,符号等字段应该会自动填充。
点击确认,这样当我们的代币有任何变动时,钱包都能收到通知了。
最后
部署合约需要以太币,Rinkeby测试网络的以太币可以通过https://faucet.rinkeby.io/免费获取。如果无法科学上网,可以直接在remix网站上体验一下。
关注我的微信公众号:
加我好友: