第八课 技术小白如何在45分钟内发行通证(TOKEN)并上线交易

551 阅读13分钟

1. 文章摘要

【本文目标】

通过逐步的指导和截图举证,一步步带领一个技术小白完成一个数字货币(通证,代币,TOKEN)的发布演示和上线交易。

【环境前置条件】

参考《第六课 技术小白如何开发一个DAPP区块链应用(以宠物商店为例)》,已在本地WIDOWS环境完成MetaMask轻钱包客户端的安装和配置;作者建议最好遵循从头开始的课程学习顺序。不过如果你想半途插入实操学习,问题也不大,遇到障碍时反向找对应文章的指导内容即可完成。

【技术收获】

从本实践中,你可以学习到: ERC20 Token的定义和实践 使用Remix Solidity IDE编写智能合约和编译调试 使用MetaMask完成钱包账户查看 使用网页钱包完成代币交易演示

【实操课程列表】

第一课 如何在WINDOWS环境下搭建以太坊开发环境 第二课 如何实现以太坊最简智能合约“Hello World”的运行 第四课 以太坊开发框架Truffle从入门到实战 第六课 技术小白如何开发一个DAPP区块链应用(以宠物商店为例) 第七课 技术小白如何在45分钟内发行通证(TOKEN)并上线交易 第八课 如何调试以太坊官网的智能合约众筹案例 第九课 如何在Remix环境下进行Solidity代码单步调试 第十课 Solidity语言编辑器REMIX指导大全

【说明】未列出的课程为知识普及的非实操类课程,所有区块链文章参考“区块链入口”专栏。

2. ERC20 Token定义和接口说明

定义

ERC20合约是在2015年11月在EIP上提出的一个合约标准,代币定义的一个标准。 Token代表数字资产,具有价值,但是并不是都符合特定的规范。 基于ERC20的货币更容易互换,并且能够在Dapps上相同的工作。 新的标准可以让token更兼容,允许其他功能,包括投票标记化。操作更像一个投票操作,Token的持有人可以完全控制资产,遵守ERC20的token可以跟踪任何人在任何时间拥有多少token。基于eth合约的子货币,所以容易实施。

ERC20 Token接口说明

方法

注意:调用者必须处理返回falsereturns (bool success).调用者绝对不能假设返回false的情况不存在。

name

返回这个令牌的名字,比如"MyToken". 可选 - 这种方法可以用来提高可用性,但接口和其他契约不能指望这些值存在。

function name() constant returns (string name)

symbol

返回令牌的符号,比如HIX. 可选 - 这种方法可以用来提高可用性,但接口和其他契约不能指望这些值存在。

function symbol() constant returns (string symbol)

decimals

返回token使用的小数点后几位, 比如 8,表示分配token数量为100000000

可选 - 这种方法可以用来提高可用性,但接口和其他契约不能指望这些值存在。

function decimals() constant returns (uint8 decimals)

totalSupply

返回token的总供应量。

function totalSupply() constant returns (uint256 totalSupply)

balanceOf

返回地址是_owner的账户的账户余额。

function balanceOf(address _owner) constant returns (uint256 balance)

transfer

转移_value的token数量到的地址_to,并且必须触发Transfer事件。 如果_from帐户余额没有足够的令牌来支出,该函数应该被throw

创建新令牌的令牌合同应该在创建令牌时将_from地址设置为0x0触发传输事件。

注意 0值的传输必须被视为正常传输并触发传输事件。

function transfer(address _to, uint256 _value) returns (bool success)

transferFrom

从地址_from发送数量为_value的token到地址_to,必须触发Transfer事件。

transferFrom方法用于提取工作流,允许合同代您转移token。这可以用于例如允许合约代您转让代币和/或以子货币收取费用。除了_from帐户已经通过某种机制故意地授权消息的发送者之外,该函数应该throw。

注意 0值的传输必须被视为正常传输并触发传输事件。

function transferFrom(address _from, address _to, uint256 _value) returns (bool success)

approve

允许_spender多次取回您的帐户,最高达_value金额。 如果再次调用此函数,它将以_value覆盖当前的余量。

注意:为了阻止向量攻击,客户端需要确认以这样的方式创建用户接口,即将它们设置为0,然后将其设置为同一个花费者的另一个值。虽然合同本身不应该强制执行,允许向后兼容以前部署的合同兼容性

function approve(address _spender, uint256 _value) returns (bool success)

allowance

返回_spender仍然被允许从_owner提取的金额。

function allowance(address _owner, address _spender) constant returns (uint256 remaining)

Events

Transfer

当token被转移(包括0值),必须被触发。

event Transfer(address indexed _from, address indexed _to, uint256 _value)

Approval

当任何成功调用approve(address _spender, uint256 _value)后,必须被触发。

event Approval(address indexed _owner, address indexed _spender, uint256 _value)

[官网接口说明点击查看],(github.com/ethereum/EI…) 接口文件ERC20Interface.sol如下:

contract ERC20Interface {

    string public constant name = "Token Name";
    string public constant symbol = "SYM";
    uint8 public constant decimals = 18;  // 18 is the most common number of decimal places
    // 0.0000000000000000001  个代币

    function totalSupply() public constant returns (uint);

    function balanceOf(address tokenOwner) public constant returns (uint balance);

    function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
    function approve(address spender, uint tokens) public returns (bool success);

    function transfer(address to, uint tokens) public returns (bool success);
    function transferFrom(address from, address to, uint tokens) public returns (bool success);


    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

#3. TOKEN合约代码 合约文件TokenERC20.sol如下:

pragma solidity ^0.4.16;

interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }

contract TokenERC20 {
    string public name;
    string public symbol;
    uint8 public decimals = 18;  // decimals 可以有的小数点个数,最小的代币单位。18 是建议的默认值
    uint256 public totalSupply;

    // 用mapping保存每个地址对应的余额
    mapping (address => uint256) public balanceOf;
    // 存储对账号的控制
    mapping (address => mapping (address => uint256)) public allowance;

    // 事件,用来通知客户端交易发生
    event Transfer(address indexed from, address indexed to, uint256 value);

    // 事件,用来通知客户端代币被消费
    event Burn(address indexed from, uint256 value);

    /**
     * 初始化构造
     */
    function TokenERC20(uint256 initialSupply, string tokenName, string tokenSymbol) public {
        totalSupply = initialSupply * 10 ** uint256(decimals);  // 供应的份额,份额跟最小的代币单位有关,份额 = 币数 * 10 ** decimals。
        balanceOf[msg.sender] = totalSupply;                // 创建者拥有所有的代币
        name = tokenName;                                   // 代币名称
        symbol = tokenSymbol;                               // 代币符号
    }

    /**
     * 代币交易转移的内部实现
     */
    function _transfer(address _from, address _to, uint _value) internal {
        // 确保目标地址不为0x0,因为0x0地址代表销毁
        require(_to != 0x0);
        // 检查发送者余额
        require(balanceOf[_from] >= _value);
        // 确保转移为正数个
        require(balanceOf[_to] + _value > balanceOf[_to]);

        // 以下用来检查交易,
        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);

        // 用assert来检查代码逻辑。
        assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
    }

    /**
     *  代币交易转移
     * 从创建交易者账号发送`_value`个代币到 `_to`账号
     *
     * @param _to 接收者地址
     * @param _value 转移数额
     */
    function transfer(address _to, uint256 _value) public {
        _transfer(msg.sender, _to, _value);
    }

    /**
     * 账号之间代币交易转移
     * @param _from 发送者地址
     * @param _to 接收者地址
     * @param _value 转移数额
     */
    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;
    }

    /**
     * 设置某个地址(合约)可以交易者名义花费的代币数。
     *
     * 允许发送者`_spender` 花费不多于 `_value` 个代币
     *
     * @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;
    }

    /**
     * 设置允许一个地址(合约)以交易者名义可最多花费的代币数。
     *
     * @param _spender 被授权的地址(合约)
     * @param _value 最大可花费代币数
     * @param _extraData 发送给合约的附加数据
     */
    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;
        }
    }

    /**
     * 销毁创建者账户中指定个代币
     */
    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;
    }

    /**
     * 销毁用户账户中指定个代币
     *
     * 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;
    }
}

函数的功能参考函数的说明描述和代码自解释。

4. 合约编译部署和发布

MetaMask钱包联网

**【前置条件】**作者假设学习者已完成MetaMask的安装和配置。还没有完成的,参考《第六课 技术小白如何开发一个DAPP区块链应用(以宠物商店为例)》的章节“5. 安装 MetaMask和配置区块链网络”,在本地WIDOWS环境完成MetaMask轻钱包客户端的安装和配置。

打开MetaMask钱包,点击左上角的“Ropsten Test Network” 连接成功。

连接成功
查看存量的账号Account 1,其中ETH余额显示为0。
存量账号Account 1余额为0
点击“Copy Address to Clipboard”,记录Account 1钱包地址为

0xD1F7922e8b78cBEB182250753ade8379d1E09949

点击MetaMask右上角环形头像的菜单“Create Account”, 创建一个新钱包账号Account 8,

新增账号成功

记录Account 8的钱包地址为

0x3D7DfB80E71096F2c4Ee63C42C4D849F2CBBE363

点击Account 8的"BUY"按钮,可以从“Ropsten Test Network” 免费获取一些测试ETH。

跳转到BUY ETH页面
点击按钮“ROPSTEN TEST FAUCET”可以看到赠送页面
点击绿色按钮,等待10秒以上,成功的话每次可以获取1个测试ETH。 【故障排查】
赠送失败
作者操作时,出现错误。从提示看,是由于用户交易拒绝。等10秒后再点击该绿色按钮则未有错误提示了。原因不明,可能是操作频繁导致。 多次点击,偶尔出错,小编一共从这个测试网站获取了5个测试ETH用于作为发币的GAS燃料。
获取测试ETH

###Remix Solidity IDE调试环境介绍 1,代码编写和编译 我们以第二课的“Hello World”智能合约为例,参考下图可完成编译和语法错误发现。

编译环境使用方法
2,合约创建 参考附图描述,在配置号MetaMask账号和网络连接的情况下,确保账户有虚拟ETH用于合约创建花销。最后点击“Create”按钮可以完成合约创建。点击输出区的网页链接可以查看合约的详细情况。
支付合约花销
image.png
3, 合约执行 参考附件路径图,点击RUN按钮可以执行一次合约。点击下方的“Say”按钮,可以看到合约输出。在调试输出窗口可以看到“call to hello.say”的运行提示。
合约执行

**【总结】**所以说,没有Ubuntu+Ganache等,直接在WINDOWS环境,也可以使用Remix+MetaMask+Ropsten Test Network组合完成一套完整的以太坊测试环境。 更详细的REMIX帮助文档参考第十课 Solidity语言编辑器REMIX指导大全

##编译ERC20智能合约 CHROME浏览器打开Remix Solidity IDE环境,打开之前编写的“TokenERC20.sol”智能合约,然后点击右侧的“start to compile”按钮。可以看到,除了一些Warning提示外,智能合约编译成功。

编译操作

##运行ERC20智能合约

运行页面
切换到"RUN"页面,Environment选择“Injected Web3”, Account自动更新为MetaMask的Account 8账号。“Create”按钮前按照发币数量(本次发1个亿),代币名称,代币符号填写为100000000,"ColorBay","CB"。 点击"Create"按钮,会弹出一个交易确认框,设置合理的Gas Price,但要确保Max Transaction Fee不会超过Account 8的ETH总数,点“SUBMIT”按钮。
智能合约部署
部署成功的话,Account8 会产生一条交易记录,显示状态为“Contract Published”,表示部署成功了。
部署成功
**【说明】**有时点击账号,会出现“Retry with a higher gas price here”的提示,一般情况下再等待10秒看看能否交易成功。万一还是不成的话,可考虑该Gas Price大点。
部署确认中

##MetaMask加载TOKEN 点击Account 8的交易记录,可以跳转智能合约部署信息显示页面:

部署成功
获取智能合约地址为0x5eeec41dc08d7caece17c4a349635934637036f1,点击可查看该交易详情。

点击MetaMask的Account 8账号的TOKENS页面的“ADD TOKEN”按钮,把代币智能合约地址Token Contract Address为0x5eeec41dc08d7caece17c4a349635934637036f1,代币标识符Token Symbol为CB,则可以创建你的代币了。

代币创建成功,一共有1亿个CB币了。此时,点击100000000的代币位置,跳转到代币查询页面, 可以在etherscan网站看到账户的持币数量了。
1亿
TOKEN信息

截止作者发稿时,以太坊的价格为2559元/个,而你有1亿个CB币,是不是快要走上人生巅峰了呢?哈哈,做个梦而已。真正的区块链应用,要能有生态,能为人类社会创造价值,而不是讲故事,割韭菜!

5. 代币交易

由于MetaMask插件没有提供代币交易功能,同时考虑到很多人并没有以太坊钱包或是被以太坊钱包网络同步问题折磨,今天我用网页钱包来讲解代币交易。 1. 进入网页钱包地址 第一次进入有一些安全提示需要用户确认。 2. 进入之后,按照下图进行设置

网络配置

3,增加自定义代币 填写地址Token Contract 为0x5eeec41dc08d7caece17c4a349635934637036f1和其他信息,点击保存按钮。

增加自定义TOKEN

配置TOKEN合约地址和代币符号
配置成功的界面如下:
**【说明】**如果不成功,则提示为“Not a valid ERC-20 token CB”,则可能是代币信息填写不正确或者右上角的网络选择错误,没有选择“Network Ropston(infura.com)”选项引起。

4.转账给Account 1 填写Account 1的地址“0xD1F7922e8b78cBEB182250753ade8379d1E09949”到“发送至地址”输入框,

选择CB,不是ETH
转账确认
交易确认
交易提示
查看账户余额,Account 8减少800万个CB币,而Account 1则增加了800万个CB币。
Account 8
Account 1
转账交易成功了! 作为一个古典投资人,用45分钟完成了TOKEN上线和交易,用4个小时整理了这篇文章。学习就是这么简单,只要你对新技术保持饥饿感,高龄开发转型区块链也不是难事!

6. 总结参考文档

本文是站在巨人的肩膀上的操作实践,感谢以下文章作者的帮助:

知识对接服务: 辉哥和欧阳哥哥在知识星球开通了区块链入门专栏,用于存放简书区块链入门专栏文章的工程源码等内容,并建立专项微信群用于技术交流,欢迎加入。