以太坊(Ethereum)是一个建立在区块链技术之上,去中心化应用平台。它允许任何人在平台中建立和使用通过区块链技术运行的去中心化应用。
以太坊代币(Ethereumtokens)是利用以太坊的智能合约编写的数字货币。
代币通常通过ICO进入公众视野。这些代币创建者会通过提供内置代币,来交换以太币、比特币或其他数字资产,比如EOS就是通过以太坊代币进行众筹。
现在市场上最大的以太坊代币是 Augur’sREP 和 Golem’sGNT。
ERC-20 标准是在2015年11月份推出的,使用这种规则的代币,表现出一种通用的和可预测的方式。通过标准化,这样代币之间的兑换和DAPP支持就会变得容易。
为了充分兼容 ERC20,开发者需要将一组特定的函数(接口)集成到他们的智能合约中,以便在高层面能够执行以下操作:
· 获得代币总供应量
· 获得账户余额
· 转让代币
· 批准花费代币
可以在https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md查看ERC20代币的标准API。
ERC20 标准定义了一个兼容协议, 需要实现的函数. 具体如下.:
contract ERC20Interface { //该方法用于获取发行的代币总数 function totalSupply() public constant returns (uint);
//该方法用于获取指定地址所拥有的代币数量 function balanceOf(address tokenOwner) public constant returns (uint balance);
//从方法用于查询_owner地址给_spender地址授权花费的额度 function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
//该方法用于调用方发起转账 function transfer(address to, uint tokens) public returns (bool success);
//该方法是发起方授权_spender可以花费发起方_value个代币 function approve(address spender, uint tokens) public returns (bool success);
//从_from地址中转出_value个代币到_to地址中 function transferFrom(address from, address to, uint tokens) public returns (bool success);
//当transfer或transferFrom被调用时,触发转账的动作 event Transfer(address indexed from, address indexed to, uint tokens);
//表示相应的授权的请求被同意,可以正式授权 event Approval(address indexed tokenOwner, address indexed spender, uint tokens); } |
同时规定了三个必须定义的变量,分别是
//代币的名称 string public constant name = "Token Name";
//代币名称的缩写 string public constant symbol = "SYM";
//代币使用的小数点后几位 uint8 public constant decimals = 18; |
这些参数的目的是指定这个代币的一些特性。以人民币为例,人民币的名称(name)是RMB,美元的代号为¥,拿100元去找零最小可以拿到零钱是一分,也就是0.0001元。因为1元最小可分割到小数点后4位(0.0001),因此最小交易单位(decimals)为4。以下为人民币,比特币,以太币的对照表供参考:
name |
symbol |
decimals |
RMB |
¥ |
4 |
Bitcoin |
BTC |
8 |
Ethereum |
ETH |
18 |
当用户写的Token的智能合约符合以上这些函数的标准,则用户的Token被称之为标准的ERC20代币。
ERC-20代币能立刻兼容以太坊钱包以太坊官网给出了发行Token的案例https://ethereum.org/token。
其中的ERC20代币代码:
pragma solidity ^0.4.16;
interfacetokenRecipient{ functionreceiveApproval(address _from, uint256 _value, address _token, bytes _extraData) external; }
contract TokenERC20 { // Public variables of the token string public name; string public symbol; uint8 public decimals = 18; // 18 decimals is the strongly suggested default, avoid changing it uint256 public totalSupply;
// This creates an array with all balances mapping (address => uint256) public balanceOf; mapping (address => mapping (address => uint256)) public allowance;
// This generates a public event on the blockchain that will notify clients 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 */ functionTokenERC20( uint256 initialSupply, string tokenName, string tokenSymbol ) public{ totalSupply = initialSupply * 10 ** uint256(decimals); // Update total supply with the decimal amount balanceOf[msg.sender] = totalSupply; // Give the creator all initial tokens name = tokenName; // Set the name for display purposes symbol = tokenSymbol; // Set the symbol for display purposes }
/** * 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; emit 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 */ functiontransfer(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 */ functiontransferFrom(address _from, address _to, uint256 _value) publicreturns (bool success) { require(_value <= allowance[_from][msg.sender]); // Check allowance allowance[_from][msg.sender] -= _value; _transfer(_from, _to, _value); returntrue; }
/** * 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 */ functionapprove(address _spender, uint256 _value) public returns (bool success) { allowance[msg.sender][_spender] = _value; returntrue; }
/** * 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 */ functionapproveAndCall(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); returntrue; } }
/** * Destroy tokens * * Remove `_value` tokens from the system irreversibly * * @param _value the amount of money to burn */ functionburn(uint256 _value) publicreturns (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 emit Burn(msg.sender, _value); returntrue; }
/** * 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 */ functionburnFrom(address _from, uint256 _value) publicreturns (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 emit Burn(_from, _value); returntrue; } } |
本文将用truffle +ganache 来发行代币。truffle和ganache开发环境的安装参考本系列的另一篇文章https://blog.csdn.net/wlhdo71920145/article/details/80476257
$ mkdir my-token $ cd my-token $ truffle init Downloading... Unpacking... Setting up... Unbox successful. Sweet!
Commands:
Compile: truffle compile Migrate: truffle migrate Test contracts: truffle test |
OpenZeppelin是一套能够给我们方便提供编写加密合约的函数库,同时里面也提供了兼容ERC20的智能合约。
$ cd my-token $ npm install zeppelin-solidity |
安装成功后可以在项目目录node_modules下查询到。
在contracts/目录下建立一个合约文件 myToken.sol
pragma solidity ^0.4.4; import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol"; contract myToken is StandardToken { string public name = "MyToken"; //代币名称 string public symbol = "MYT"; //代币简称 uint8 public decimals = 4; uint256 public INITIAL_SUPPLY = 1000000; //发行总量 function myToken () { balances[msg.sender] = INITIAL_SUPPLY; } } |
启动Ganache
ganache-cli -p 60545 |
修改配置文件truffle.js和truffle-config.js:
module.exports = { // See // for more about customizing your Truffle configuration! networks: { development: { host: "127.0.0.1", port: 60545, network_id: "*" // Match any network id } } }; |
创建文件migrations/2_deploy_contracts.js
const myToken = artifacts.require("./myToken.sol")
module.exports = function(deployer) { deployer.deploy(myToken); }; |
编译部署
$ truffle compile $ truffle migrate --network development |
启动控制台执行合约来代币
$ truffle console --network development truffle(development)> var myTokenInstance undefined truffle(development)>myToken.deployed().then(function(instance){myTokenInstance = instance}) undefined truffle(development)> myTokenInstance.balanceOf(web3.eth.accounts[0]) { [String: '1000000'] s: 1, e: 6, c: [ 1000000 ] } |
可以看到自己的代币创建完成,总量1000000,已经分配置给主账户,因为他是合约部署的账户
查询代币的合约地址
truffle(development)> myTokenInstance.address '0x23138de749202c7a2febc2a11c14b3e8a44d3e32' |
现在通过钱包MetaMask来查询代币,首先配置MetaMask连接到Ganache的测试网络:
然后导入账户,Ganache启动的时候会生成账户和PrivateKey,获取第一个账户的PrivateKey:
Ganache CLI v6.1.0 (ganache-core: 2.1.0)
Available Accounts ================== (0) 0x4e46cb4e659ab6529a76050047b46a9550f40b73 (1) 0x3deb41052edefae8c405e316d05e1ae1fd8a440d (2) 0x7d1d633567d8513ef4289bf377cc499cb11fb6d1 (3) 0x1842efd93fcc83bd89ff99d1620f0294fd56c896 (4) 0x23f63b6e88b9dcb4f20ca98fa5e64d52c62245cb (5) 0xd0d0f981b90e5738dfcb8d92f9739a0543ff4ec6 (6) 0xe32c2ed2d28a25d1e6054ef507eeba6fe91e520e (7) 0x462a0aac15c6f23995ca38a836df808f6a857b7c (8) 0x18ebb0c8d9979f82d220efff1e9766faf2f1b396 (9) 0xd46dc33270a069e6d53eb1436a6add923150c05d
Private Keys ================== (0) 51ba9be2099116ce70163a01854584af8c20e292d6ac05fc507ea74d05db3128 (1) c8555c81fff7ded0a1de56ce584fad72385937da15e15bb7fc83df3d58ea765d (2) 4143654a28bdef6d5926143002cdbab6a15552895ddf5595b82d17158b217677 (3) 2a10cc50ca4a3a25928f59d50bbe18c964b1ab41203a0c8e2b77b639bbf52099 (4) a059395f63d63d22d4cd86428df6ff39d78996947439b87da41a12b44a8f8233 (5) eba6aef32844ff0099a463cb3d8d1ca84e5716c94285bbb8cbb609742fe96d63 (6) 9404a5a0e7a084129355dfbe9f0cefa7abd69c1e6da3863ac2790727487a6214 (7) 2c7aa55cb1a71ef1632140de575c264d3ae370c5c9a846f47558083e19b20457 (8) daf0936b531e338fe75081d9ae688c7ebf1f9971f3d82ca53384c96f54c2643e (9) ba9d0dcfed2e1dfb9be37aad1aa67b00f8cd5051ba2d0c32c8a52dbe89099d37 |
在MetaMask界面导入账户:
点击add token 添加token,输入合约地址:
Token添加成功后就可以查询到我们的代币了: