一夜身价暴涨千倍,如何发布你自己的 ICO?
本文来自作者 余博伦 在 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)<
- 上一篇: 网站性能优化从入门到粗通(PHP 篇)
- 下一篇: 区块链与程序员:赚钱还是创业