入门客AI创业平台(我带你入门,你带我飞行)
博文笔记

一夜身价暴涨千倍,如何发布你自己的 ICO?

创建时间:2018-02-13 投稿人: 浏览次数:131


本文来自作者 余博伦  GitChat 上分享 「韭菜种植与收割:发布你自己的 ICO」,阅读原文查看交流实录。

文末高能

编辑 | 哈比

你可曾想梭哈全部存款,参与 ICO,一夜身价暴涨千倍,获得财富自由,从此走上人生巅峰?

ICO 是借用 IPO 生造出来的一种概念,同样具有非常相似的募资机制,但 IPO 有着严格的上市流程、政策监管,如下图所示。

即便如此,参与 IPO 仍然有着相当大的风险,且为股市带来了相当大的不稳定因素。而与 ICO 比起来简直就是小巫见大巫了。

一家公司想要进行 IPO 起码要达到能够上市的标准,而想发布 ICO 你只要有一个好听的 idea 就足够了。并且严重缺乏监管,虽然各国政府都在不断发出声明,但截至本分享写作前,也没有正式出台比较明朗的有关规定。

这也导致无数的空气项目披着虚拟货币和区块链的高科技壳,到处招摇撞骗割韭菜,有过之无不及的还搞什么 AI+ 区块链,IOT+ 区块链,技术名词堆积越多的项目,死得往往越快。

甚至一些有头有脸的大公司,也忍不住打打擦边球,收割一波,炒作炒作,股价就能翻几个涨停。

可就像马老爷子说的:

如果有 10% 的利润,它就保证到处被使用;有 20% 的利润,它就活跃起来;有 50% 的利润,它就铤而走险;为了 100% 的利润,它就敢践踏一切人间法律;有 300% 的利润,它就敢犯任何罪行,甚至绞首的危险。

即便如此,仍然有很多人跃跃欲试不信邪。这一场 Chat 就手把手教你为 ICO 做好所有技术面上的准备。在和大家一起点亮新技能的同时,也揭一揭所谓 ICO 的老底。

内容概要

目前市场上 99% 的项目 ICO 都是基于以太坊(Ethereum)智能合约(Smart Contracts)技术发布的 token(ERC20 Token)

本次分享也是基于这一套技术栈,介绍内容包括以下几个方面。

  • 本地开发环境构建

  • 以太坊智能合约开发

    • 本地开发环境发布

    • 线上测试网络发布

    • 主网络发布

    • ERC20 Token 合约开发

    • ICO Crowdsale 合约开发

    • 补充说明与权限控制

    • 合约的发布及调试

  • Dapp 开发

    • web3.js 的使用

    • Metamask 简介

    • truffle-contract 的使用

    • ICO 前端应用开发

  • Dapp 部署

    • IPNS

    • Nginx 反向代理

    • IPFS 简介

    • 发布应用

    • 域名解析

使用到的技术栈包括:

  • Truffle:http://truffleframework.com

  • [Ganache:http://truffleframework.com/ganache

  • Metamask:https://metamask.io

  • Solidity:http://solidity.readthedocs.io/en/develop

  • openzeppelin:https://openzeppelin.org

  • Infura:https://infura.io

  • web3.js:https://web3js.readthedocs.io/en/1.0/index.html

  • truffle-contract:https://github.com/trufflesuite/truffle-contract

  • ipfs:https://ipfs.io

对读者的基本要求有:

  • 了解编程

  • 会 JavaScript

本地开发环境构建

以太坊官方提供的 Mist (https://github.com/ethereum/mist/releases) 和 Ethereum-Wallet (https://github.com/ethereum/mist/releases)

其中 Mist 是一个可以用来访问 Dapp 的浏览器,Ethereum-Wallet 是 Mist 的一个独立发布版本,也算是浏览器,但只能用来访问以太坊钱包这个应用。

在网络同步过程中或多或少都会遇到问题,而且目前网络拥堵,完整节点过大,同步完成相当困难。但事实上我们进行以太坊开发时并不需要同步完整的节点,也可以选择使用相应的模拟开发环境。

Truffle (http://truffleframework.com) 框架为你提供本地进行智能合约开发的所有依赖支持,使你可以在本地进行智能合约及 Dapp 的开发、编译、发布。安装非常简单,只需要:

npm install -g truffle

Ganache (http://truffleframework.com/ganache) 也是 Truffle 框架中提供的一个应用,可以在你的本地开启模拟一个以太坊节点,让你能够将开发好的智能合约发布至本地测试节点中运行调试。

安装也非常简单,官网下载即可,双击打开运行。

不过这里有一个隐藏的坑,如果你使用的是 Windows 系统的话,Ganache 提供的是后缀名为 .appx 的 Windows 应用商店版安装包。你需要打开 Windows 设置 -> 系统 -> 针对开发人员 -> 选择 “旁加载应用” 这个选项。

确认之后就可以双击 Ganache.appx 进行安装了,假如系统仍然无法识别这一后缀名,你可以手动打开 powershell 输入如下命令进行安装。

Add-AppxPackage .Ganache.appx

至此本地开发智能合约及 Dapp 的环境就算安装完成了,Truffle 官方提供了许多示例教程以及应用脚手架(truffle box),其中就包括教你开发以太坊宠物商 (http://truffleframework.com/tutorials/pet-shop) 的教程等内容。

在此不再赘述,感兴趣的同学自己动手可以试试。

以太坊智能合约开发

首先使用 Truffle 初始化我们的项目,命令如下。

mkdir my-icocd my-ico npm init -y truffle init

脚本运行完成之后 Truffle 会自动为我们的项目创建一系列文件夹和文件,如下图所示。

这里有一个隐藏的坑,如果你使用 Windows 命令行的话,需要删掉 truffle.js 文件,否则在项目目录执行 truffle 相关命令时,CMD 会混淆 truffle 与 truffle.js 文件。

因此,你应该将配置写在 truffle-config.js 文件当中。

ERC20 Token 合约开发

现在我们的项目目录大概是这个样子:

  • contracts/

    • Migrations.sol

  • migrations/

    • 1_initial_migration.js

  • test/

  • package.json

  • truffle-config.js 或 truffle.js

我们在编写智能合约时,需要在 contracts 目录下新建相应的智能合约文件。

在以太坊开发智能合约的编程语言叫做 Solidity (https://goo.gl/hCHh3w)。它是一种在语法上非常类似 JavaScript 的语言,其后缀名为 .sol 。

例如在这里我们可以创建一个名为 GitCoin.sol 的文件,命令如下。

// *nix touch GitCoin.sol // win copy NUL > GitCoin.sol

ERC20(Ethereum Request for Comments NO.20)(https://goo.gl/aX4x5F是官方发行的 token 标准。

如果你希望你发布的 token 能够在以太坊网络上流通、上市交易所、支持以太坊钱包,在开发 token 的合约时就必须遵从这一规范。

ERC20 规定了合约中的一系列变量、方法、事件,你可以参考官网教程 Create your own CRYPTO-CURRENCY with Ethereum (https://www.ethereum.org/token) 当中的示例代码:

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;    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);    /**     * Constrctor function     *     * Initializes contract with initial supply tokens to the creator of the contract     */    function TokenERC20(        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;        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;    } }

我只是想割韭菜而已,用得着写几百行代码吗?

当然不必,这时我们就需要使用到智能合约开发框架 OpenZeppelin (https://openzeppelin.org),安装命令如下。

npm install zeppelin-solidity --save

GitCoin.sol

引入 OpenZeppelin,代码如下。

// 声明 solidity 编译版本pragma solidity ^0.4.18;// 引入框架为我们提供的编写好的 ERC20 Token 的代码import "zeppelin-solidity/contracts/token/StandardToken.sol";// 通过 is 关键字继承 StandardTokencontract GitToken is StandardToken {  string public name = "GitToken"; // Token 名称  string public symbol = "EGT"; // Token 标识 例如:ETH/EOS  uint public decimals = 18; // 计量单位,和 ETH 保持一样就设置为 18  uint public INITIAL_SUPPLY = 10000 * (10 ** decimals); // 初始供应量  // 与 contract 同名的函数为本 contract 的构造方法,类似于 JavaScript 当中的 constructor  function GitToken() {    totalSupply = INITIAL_SUPPLY; // 设置初始供应量    balances[msg.sender] = INITIAL_SUPPLY; // 将所有初始 token 都存入 contract 创建者的余额  } }

好了,至此一个可以用来交易的符合 ERC20 标准的 token 就编写完毕了。

就这么简单?就这么简单!当然智能合约的功能不止如此,token 中可以玩转设计的地方也不止这些。

不过我们要稍微放在后面一些来讨论,接下来还是赶快着手 ICO 合约开发,为我们的项目募集资金吧。

ICO Crowdsale 合约开发

同样,以太坊官网文档在教程 CROWDSALE Raising funds from friends without a third party (https://www.ethereum.org/crowdsale) 中也为我们提供了用来 crowdsale 做 ICO 募资的示例代码:

pragma solidity ^0.4.18;/** * interface 的概念和其他编程语言当中类似,在这里相当于我们可以通过传参引用之前发布的 token 合约 * 我们只需要使用其中的转账 transfer 方法,所以就只声明 transfer **/interface token {    function transfer(address receiver, uint amount); }contract Crowdsale {    // 这里是发布合约时需要传入的参数    address public beneficiary; // ICO 募资成功后的收款方    uint public fundingGoal; // 骗多少钱    uint public amountRaised; // 割到多少韭菜    uint public deadline; // 割到啥时候    /**    * 卖多贵,即你的 token 与以太坊的汇率,你可以自己设定    * 注意到,ICO 当中 token 的价格是由合约发布方自行设定而不是市场决定的    * 也就是说你项目值多少钱你可以自己编    **/    uint public price;    token public tokenReward; // 你要卖的 token    mapping(address => uint256) public balanceOf;    bool fundingGoalReached = false; // 是否达标    bool crowdsaleClosed = false; // 售卖是否结束    /**    * 事件可以用来记录信息,每次调用事件方法时都能将相关信息存入区块链中    * 可以用作凭证,也可以在你的 Dapp 中查询使用这些数据    **/    event GoalReached(address recipient, uint totalAmountRaised);    event FundTransfer(address backer, uint amount, bool isContribution);    /**     * Constrctor function     *     * Setup the owner     */    function Crowdsale(        address ifSuccessfulSendTo,        uint fundingGoalInEthers,        uint durationInMinutes,        uint etherCostOfEachToken,        address addressOfTokenUsedAsReward    ) {        beneficiary = ifSuccessfulSendTo;        fundingGoal = fundingGoalInEthers * 1 ether;        deadline = now + durationInMinutes * 1 minutes;        price = etherCostOfEachToken * 1 ether;        tokenReward = token(addressOfTokenUsedAsReward); // 传入已发布的 token 合约的地址来创建实例    }    /**     * Fallback function     *     * payable 用来指明向合约付款时调用的方法     */    function () payable {        require(!crowdsaleClosed);        uint amount = msg.value;        balanceOf[msg.sender] += amount;        amountRaised += amount;        tokenReward.transfer(msg.sender, amount / price);        FundTransfer(msg.sender, amount, true);    }    /**    * modifier 可以理解为其他语言中的装饰器或中间件    * 当通过其中定义的一些逻辑判断通过之后才会继续执行该方法    * _ 表示继续执行之后的代码    **/    modifier afterDeadline() { if (now >= deadline) _; }    /**     * Check if goal was reached     *     * Checks if the goal or time limit has been reached and ends the campaign     */    function checkGoalReached() afterDeadline {        if (amountRaised >= fundingGoal){            fundingGoalReached = true;            GoalReached(beneficiary, amountRaised);        }        crowdsaleClosed = true;    }    /**     * Withdraw the funds     *     * Checks to see if goal or time limit has been reached, and if so, and the funding goal was reached,     * sends the entire amount to the beneficiary. If goal was not reached, each contributor can withdraw     * the amount they contributed.     */    function safeWithdrawal() afterDeadline {        if (!fundingGoalReached) {            uint amount = balanceOf[msg.sender];            balanceOf[msg.sender] = 0;            if (amount > 0) {                if (msg.sender.send(amount)) {                    FundTransfer(msg.sender, amount, false);                } else {                    balanceOf[msg.sender] = amount;                }            }        }        if (fundingGoalReached && beneficiary == msg.sender) {            if (beneficiary.send(amountRaised)) {                FundTransfer(beneficiary, amountRaised, false);            } else {                //If we fail to send the funds to beneficiary, unlock funders balance                fundingGoalReached = false;            }        }    } }

至此我们的 ICO 合约也开发完毕了,基本上一行代码都没有写,只是改了几个参数,一个键盘上只有三个按键的程序员都能够完成这类智能合约的开发,没有比这更友好的编程体验了。

虽然 solidity 是一种非图灵完备的编程语言,但我们仍然能够用它编写许多逻辑。

上述的 ICO 示例代码写得算比较客气的一种,在最后的提款方法中,如果筹资达标,ICO 发布方则可以取走所有筹款,而如果未达标,参与者则能够取回自己的投资,由合约来持有所有款项。

但事实上,我们仍然可以随意修改其中的逻辑,看下面代码。

function () payable {  require(!crowdsaleClosed);  uint amount = msg.value;  balanceOf[msg.sender] += amount;  amountRaised += amount;  tokenReward.transfer(msg.sender, amount / price);  // 每次有人付款直接取走筹资  beneficiary.send(amountRaised);  amountRaised = 0;  FundTransfer(msg.sender, amount, true); }// 删除剩余代码

补充说明与权限控制

既然咱是铁了心来割韭菜的,如此简单的代码怎么能够满足咱的贪欲呢?一定要学比特币固定供给量吗?

我是来卖 token 的呀,万一有一天卖完了怎么办,万一有人手里筹码比我自己都多了控盘怎么办,万一发的数量太多卖的不好怎么办?

事实上解决这些问题的逻辑全部都可以写在智能合约里。

Ownable token

在我们的潜在观念里,区块链自有不可变属性。

这种不可变属性在一些狂热信徒的演绎当中变成了平权属性,甚至带有了共产主义色彩,仿佛拥抱区块链技术就能够为未来的人类文明带来希望,把人民从集权的手中解救出来。

然而事实上这种不可变性同样是两面的,它能够带来的也包括所有权的不可变性。

ERC20 标准只规定了我们的合约中应该包含哪些方法,而没有限制合约中不能出现哪些方法,因此在之前的基础上,我们还可以继续编写一些特殊的方法,赋予合约发布者一些管理员特权。

请看下面代码:

contract Ownable {    address public owner;    function Ownable() public {        owner = msg.sender;    }    // 通过 onlyOwner 我们可以限定一些方法只有所有者才能够调用    modifier onlyOwner {        require(msg.sender == owner);        _;    }    function transferOwnership(address newOwner) onlyOwner public {        owner = newOwner;    } }// 合约可以同时有多个继承contract GitToken is StandardToken, Ownable {  ...

MintableToken

接下来我们来解决 token 不够卖的问题,万一我的 initial offer 卖断货了怎么办,万一我卖完一次还想卖怎么办?

这时我们就需要把 token 编写成为 MintableToken,在我们想增发的时候就能增发,代码如下:

// 用 onlyOwner 限定只有 token 的所有者才能够进行增发操作function mint(address _to, uint256 _amount) onlyOwner public returns (bool) {  totalSupply_ = totalSupply_.add(_amount);  balances[_to] = balances[_to].add(_amount);  Mint(_to, _amount);  Transfer(address(0), _to, _amount);  return true; }

BurnableToken

万一我们的 token 不小心发了太多,卖的时间久了贬值怎么办?

当然是销毁了,可参照下面代码:

/** * 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; }

万一有人手里的筹码太多,或者 token 被竞争对手买走了怎么办?没关系,我们还可以指定销毁某一账户中的 token,请看下面代码:

/**  * 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)<

声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。