如何在以太坊上发行自己的代币

简单代币开发

代币(Token):

代币单纯从其名字上理解的话,就是一种可以替代通用货币起到交换媒介作用的东西,可以是商场积分,可以是游戏币,也可以是赌场筹码。但是在区块链中,就不完全是那么回事了,区块链中的代币或者说Token通常指的是具有流通性的加密数字权益证明,例如比特币、以太币等数字货币都属于代币

从以上定义可以得知代币的三个要素:

  • 权益证明:一种数字形式存在的权益凭证,代表一种权利,一种固有的内在价值和使用价值
  • 加密:为了防止篡改,保护隐私,不可以复制等
  • 较高的可流通性(去中心化):可以进行交易,兑换通用或法定货币等,之所以要去中心化是因为中心化的代币都存在平台限制及信任问题,所以其流通性就会受到局限

综上,简言之代币的概念大致上就是基于区块链发行的加密货币或者其他类似的东西。通常大多数人或团队在开发区块链项目时,都会考虑发行自己的代币。在最初的时候,我们要发行自己的加密货币得从比特币的源码上改造出来。不过现如今通过以太坊平台,我们能够很方便的开发并发行自己的代币,所以本文将介绍如何基于以太坊开发自己的代币。

本文将使用到两个工具,分别是Remix和MetaMask,如果对这两个工具不太了解的话可以参考我另外两篇文章:

首先我们使用solidity开发一个拥有最基本功能的“代币”demo,以便了解代币最基础的样子,代码如下:

pragma solidity ^0.4.20;

contract SimpleToken {
    
    // 保存每个地址所拥有的代币数量
    mapping (address => uint256) public balanceOf; 
    
    // 参数为代币的初始供应量或者说发行数量
    constructor (uint256 initSupply) public {
        // 创建者拥有所有的代币
        balanceOf[msg.sender] = initSupply;
    }
    
    /**
     * 转移代币
     * 
     * @param _to     接收方的账户地址
     * @param _value  转移的代币数量
     */
    function transfer (address _to, uint256 _value) public {
        // 发送方的账户余额需大于等于转移的代币数量
        require(balanceOf[msg.sender] >= _value);
        // 检查有没有发生溢出,因为数量有可能超过uint256可存储的范围
        require(balanceOf[_to] + _value >= balanceOf[_to]);
        
        // 减少发送方账户的代币数量
        balanceOf[msg.sender] -= _value;
        // 增加接收方账户的代币数量
        balanceOf[_to] += _value;
    }
}

1.代码编写完成后,在remix上部署合约,首先初始化代币供应量:


image.png

2.查看指定账户所拥有的代币数量:


image.png

3.转移代币到另一个账户地址:


image.png

4.此时该账户地址的代币数量就只剩下100了:


image.png

ERC-20标准简述

在上一小节中,我们实现了一个简单的代币,但就因为简单,所以该代币是极其不完善的,例如没有代币名称、代币符号以及无法控制代币的交易等。

因此以太坊定义了ERC20,ERC20是以太坊定义的一个代币统一标准,该标准描述了我们在实现代币时必须要遵守的协议,如指定代币名称、总量、实现代币交易函数等,只有实现了该协议的代币才能被以太坊钱包支持。

下图是一个实现了ERC20协议的代币:


image.png

实际上ERC20协议是定义了一组标准接口,该协议使得在以太坊上开发的任何代币都能被其他应用程序重用,例如钱包到分散的交易所等,如下:

contract ERC20Interface {
    // 代币名称
    string public constant name = "Token Name";
    // 代币符号或者说简写
    string public constant symbol = "SYM";
    // 代币小数点位数,代币的最小单位, 18表示我们可以拥有 0.0000000000000000001单位个代币
    uint8 public constant decimals = 18;  // 18 is the most common number of decimal places

    // 查询代币的发行总量
    function totalSupply() public constant returns (uint);
    // 查询地址的代币余额
    function balanceOf(address tokenOwner) public constant returns (uint balance);
    // 查询spender允许从tokenOwner上花费的代币数量
    function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
    // 实现代币交易,用于从本账户给某个地址转移代币
    function transfer(address to, uint tokens) public returns (bool success);
    // 允许spender多次从你的账户取款,并且最多可取tokens个,主要用于某些场景下授权委托其他用户从你的账户上花费代币
    function approve(address spender, uint tokens) public returns (bool success);
    // 实现代币用户之间的交易,从一个地址转移代币到另一个地址
    function transferFrom(address from, address to, uint tokens) public returns (bool success);

    // 代币交易时触发的事件,即调用transfer方法时触发
    event Transfer(address indexed from, address indexed to, uint tokens);
    // 允许其他用户从你的账户上花费代币时触发的事件,即调用approve方法时触发
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

ERC-20标准代币的实现

代码如下:

pragma solidity ^0.4.20;

// 定义ERC-20标准接口
contract ERC20Interface {
    // 代币名称
    string public name;
    // 代币符号或者说简写
    string public symbol;
    // 代币小数点位数,代币的最小单位
    uint8 public decimals;
    // 代币的发行总量
    uint public totalSupply;

    // 实现代币交易,用于给某个地址转移代币
    function transfer(address to, uint tokens) public returns (bool success);
    // 实现代币用户之间的交易,从一个地址转移代币到另一个地址
    function transferFrom(address from, address to, uint tokens) public returns (bool success);
    // 允许spender多次从你的账户取款,并且最多可取tokens个,主要用于某些场景下授权委托其他用户从你的账户上花费代币
    function approve(address spender, uint tokens) public returns (bool success);
    // 查询spender允许从tokenOwner上花费的代币数量
    function allowance(address tokenOwner, address spender) public view returns (uint remaining);

    // 代币交易时触发的事件,即调用transfer方法时触发
    event Transfer(address indexed from, address indexed to, uint tokens);
    // 允许其他用户从你的账户上花费代币时触发的事件,即调用approve方法时触发
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

// 实现ERC-20标准接口
contract ERC20Impl is ERC20Interface {
    // 存储每个地址的余额(因为是public的所以会自动生成balanceOf方法)
    mapping (address => uint256) public balanceOf;
    // 存储每个地址可操作的地址及其可操作的金额
    mapping (address => mapping (address => uint256)) internal allowed;

    // 初始化属性
    constructor() public {
        name = "Test Token";
        symbol = "TEST"; 
        decimals = 18;
        totalSupply = 100000000;
        // 初始化该代币的账户会拥有所有的代币
        balanceOf[msg.sender] = totalSupply;
    }

    function transfer(address to, uint tokens) public returns (bool success) {
        // 检验接收者地址是否合法
        require(to != address(0));
        // 检验发送者账户余额是否足够
        require(balanceOf[msg.sender] >= tokens);
        // 检验是否会发生溢出
        require(balanceOf[to] + tokens >= balanceOf[to]);

        // 扣除发送者账户余额
        balanceOf[msg.sender] -= tokens;
        // 增加接收者账户余额
        balanceOf[to] += tokens;

        // 触发相应的事件
        emit Transfer(msg.sender, to, tokens);
    }

    function transferFrom(address from, address to, uint tokens) public returns (bool success) {
        // 检验地址是否合法
        require(to != address(0) && from != address(0));
        // 检验发送者账户余额是否足够
        require(balanceOf[from] >= tokens);
        // 检验操作的金额是否是被允许的
        require(allowed[from][msg.sender] <= tokens);
        // 检验是否会发生溢出
        require(balanceOf[to] + tokens >= balanceOf[to]);

        // 扣除发送者账户余额
        balanceOf[from] -= tokens;
        // 增加接收者账户余额
        balanceOf[to] += tokens;

        // 触发相应的事件
        emit Transfer(from, to, tokens);   

        success = true;
    }

    function approve(address spender, uint tokens) public returns (bool success) {
        allowed[msg.sender][spender] = tokens;
        // 触发相应的事件
        emit Approval(msg.sender, spender, tokens);

        success = true;
    }

    function allowance(address tokenOwner, address spender) public view returns (uint remaining) {
        return allowed[tokenOwner][spender];
    }
}

将以上代码复制到Remix上,编译合约代码:

image.png

部署合约:


image.png

部署成功:


image.png

测试balanceOf方法:


image.png

测试transfer方法:


image.png

在Remix上测试完基本的方法后,我们通过MetaMask在以太坊的测试网络上发行我们的代币。打开MetaMask选择Ropsten测试网络,如下:

image.png

注:如果你没有余额请点击DEPOSIT按钮,然后再点击GET ETHER按钮,会进入一个网站,在该网站中点击request 1 ether from faucet,可以送一些测试网络的以太币给你:


image.png

然后到Remix IDE上刷新一下,此时Remix会自动选择相应的测试网络,注意MetaMask和Remix需要在同一个浏览器上,并且Environment和Account和MetaMask保持一致,如下:


image.png

点击Deploy后,这时MetaMask会弹出一个交易确认框,点CONFIRM:


image.png

待合约部署交易确认之后,复制合约地址:


image.png

打开Metamask界面,打开菜单(Menu),点ADD TOKENS:


image.png

出现如下对话框,选择Custom Token,然后将合约地址粘贴进去:


image.png

点ADD TOKENS:


image.png

这时在MetaMask里就可以看到我们创建的代币了,如下:


image.png

接下来我们测试一下转移代币,选择我们刚刚部署的代币,点击SEND:


image.png

填写转账的相关信息:


image.png

确认转账:


image.png

成功后会生成相应的交易记录:


image.png

如此一来我们就完成了代币的创建和部署(正式网络和测试网络部署方法一样),现在已经可以在Etherscan查询到我们刚刚发行的代币了:

image.png

查看代币详情

image.png


关于发行总量为0的问题

在上一小节中,我们已经成功在以太坊上发行了我们自己的代币,但许多同学应该也已经产生了一个疑问,为啥明明代币中设置了代币的发行数量,但是发行的数量还是为0呢?其实这也是新手在开发代币时可能会遇到的一个坑,之所以我们看到发行总量为0是因为代币的发行总量的整数位表示并不是totalSupply的值,而是totalSupply除以10的decimals次方。

例如我们将decimals设置为8,那么就需要以代币的发行总量(totalSupply)除以100000000,才能得到其整数。上一小节中合约代码里设置的decimals值为18,代表代币使用的小数位为18位,而totalSupply的值为1亿,自然计算出来就是小于1的小数,而且还是比较小的小数,MetaMask只显示3位小数,所以导致在我们看来代币的发行总量为0。

既然知道是什么原因导致的,那么解决起来就好办了,修改合约的构造器代币如下:

// 初始化属性
constructor() public {
    decimals = 18;
    totalSupply = 100000000 * 10 ** uint256(decimals);
    name = "Test Token";
    symbol = "TEST"; 
    // 初始化该代币的账户会拥有所有的代币
    balanceOf[msg.sender] = totalSupply;
}

修改完成后,依旧按照我们上一小节所描述的流程去发行代币并将代币添加到MetaMask钱包中。此时该代币的发行总量就应该是我们期望的值了,如下:


image.png

我们也可以尝试使用代币进行转账:


image.png

转账成功,该账户的余额也正常扣减了:


image.png

另一个账户也正常收到了这100个代币:


image.png

然后我们再去查看一下代币详情

image.png

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容