区块链100讲:今天想要教大家发个币~

image

本讲将通过一些简单的例子从近处看以太坊DApp的开发细节。偷偷告诉你,本文会涉及到以太坊中的一个热门场景:“发币”,满足一下各位苦逼的开发当一回大佬的愿望 ;)

1

背景

本文用到的开发工具:

  • Node

  • Truffle

相关的包:

  • yargs,cli库

  • web3,json-rpc抽象

  • truffle-contract,合约抽象

  • openzeppelin-solidity,安全合约库

文章中创建的项目为一个“node + truffle”工程,对外提供cli。这个cli暴露了两条命令:

$./app.js help

app.js [命令]

命令:

  app.js simple-data <action> [from]        access simple-data contract from an

  [value]                                   external address.

  app.js myico <command> [purchaser]        commands about my ico.

  [value]

选项:

  --version  显示版本号                                                   [布尔]

  --help     显示帮助信息                                                 [布尔]

选择以cli而非gui的方式作为dapp的前端主要的理由:

  • 当前的重点是迅速掌握以太坊dapp开发的套路。

  • cli相比起gui来讲,省去了很多麻烦事。而且,我对于gui的开发,实在兴趣不大。

2

准备

那么,让我们先来准备工程的架子:

  • mkdir 目录 && cd 目录

  • npm init

  • truffle init

  • npm install yargs –save

执行完成后,cli工程需要基本环境就都具备了。之后,在项目的工程根下创建app.js,它将作为整个工程的入口。并且工程采用Command Module(https://github.com/yargs/yargs/blob/master/docs/advanced.md#providing-a-command-module)的方式组织。

app.js的内容如下:

#!/usr/bin/env node

require('yargs')
 .command(require('./simple-data.js'))
 .command(require('./myico.js'))
 .help()
 .argv

其中的两条命令以module方式组织,分别对应:

  • simple-data,简单合约交互

  • my i-c-o,i-c-o合约交互

3

simple-data

simple-data命令展示了一个简单的前端和合约交互的例子,为编写复杂交互提供了参考。它的整个过程如下:

(1)npm install web3 –save

(2)npm install truffle-contract –save

(3)编写合约

pragma solidity ^0.4.23;

contract SimpleData {

    address public owner;

        uint data; 

   constructor() public {

        owner = msg.sender;

    }

    function set(uint x) public{

        data = x;

    }

    function get() view public returns (uint) {

        return data;

    }

}

(4)编写Migration文件

const SimpleData = artifacts.require("./SimpleData.sol");

module.exports = function(deployer) {
 deployer.deploy(SimpleData);
};

(5)编写Command

const Web3 = require('web3');

const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:9545"));

const contract = require('truffle-contract');

const SimpleDataContract = require('./build/contracts/SimpleData.json');

const simpleData = contract(SimpleDataContract);

simpleData.setProvider(web3.currentProvider);

if (typeof simpleData.currentProvider.sendAsync !== "function") {

    simpleData.currentProvider.sendAsync = function() {

        return simpleData.currentProvider.send.apply( 

           simpleData.currentProvider, arguments

        );

    };

}

exports.command = 'simple-data <action> [from] [value]';

exports.describe = 'access simple-data contract from an

 external address.';exports.handler = function(argv) {

    if(argv.action == 'get') { 

       simpleData.deployed().then(function(instance){

            instance.get().then(function(result){

                console.log(+result);

            })

        });

    } else if(argv.action == 'set') {

        if(!argv.value) {

            console.log('No value provided!'); 

           return;

        }

        simpleData.deployed().then(function(instance){

            instance.set(argv.value, {from: argv.from}).then(function(result){

                console.log(result);

            }) 

       });

    } else {

        console.log('Unknown action!');

    }

}

说明:

  • http://localhost:9545”对应“truffle develop”环境中的端口。

  • “./build/contracts/SimpleData.json”由“truffle compile”产生,这个json文件将和truffle-contract一起配合产生针对于合约的抽象。

  • “if(typeof … !== “function”) { … }”这个if block是对于truffle-contract这个issue的walkaround。

  • 随后的exports则是yargs command module的接口要求。对于命令:“simple-data [from] [value]”,其格式由yargs解析:<…>,必填;[…],选填

编译部署之后,就可以简单的试用了(先给app.js可执行权限):

  • app.js simple-data get

  • app.js simple-data set 地址 值

4

my–i-c-o

接下来,就到了最让人期待的时刻:发币,更准确的说是基于ERC 20的代币发放。因为以太坊中各个币种有不同的含义和用途,比如另一个常见的币种:ERC 721,它发行的每个token都是独一无二不可互换的,比如以太猫。

关于发币,究其本质就是实现特定的合约接口。从开发的投入产出比来讲,我建议采用OpenZeppelin:

  • 跟钱打交道的事情需要慎重,由不安全合约导致的问题屡见不鲜。

  • 自己编写安全合约并不简单。

  • OpenZeppelin包含了当前安全合约的最简实践,其背后的公司本身就提供安全审计服务。

  • 可复用合约代码加快了合约的开发。

以下是使用OpenZeppelin开发i-c-o的过程:

(1)npm install -E openzeppelin-solidity

(2)i-c-o的过程由两个合约组成,它们都将直接基于OpenZeppelin的合约完成。

  • coin,代币

  • crowdsale,众筹

(3)代币合约

pragma solidity ^0.4.23;

import "../node_modules/openzeppelin-solidity/contracts/token/ERC20/MintableToken.sol";

contract MyCoin is MintableToken {

    string public name = "MY COIN";   // 代币名称

    string public symbol = "MYC";    // 代币代码

    uint8 public decimal = 18;      // 位数

}

(4)众筹合约

pragma solidity ^0.4.23;

import "../node_modules/openzeppelin-solidity/contracts/crowdsale/emission/MintedCrowdsale.sol";

import "../node_modules/openzeppelin-solidity/contracts/crowdsale/validation/TimedCrowdsale.sol";

contract MyCrowdsale is TimedCrowdsale, MintedCrowdsale { 

   constructor (

        uint256 _openingTime,

        uint256 _closingTime,

        uint256 _rate,

        address _wallet, 

       MintableToken _token

        ) 

        public

        Crowdsale(_rate, _wallet, _token)

        TimedCrowdsale(_openingTime, _closingTime) {

    }

}

几行代码就完成了核心合约的开发,这要归功于咱们选的框架,;)

(5)Migration脚本

const MyCoin = artifacts.require("./MyCoin.sol");

const MyCrowdsale = artifacts.require("./MyCrowdsale.sol");

module.exports = function(deployer, network, accounts) {

    const openingTime = web3.eth.getBlock('latest').timestamp + 2;

    const closingTime = openingTime + 3600; 

   const rate = new web3.BigNumber(1000);

    const wallet = accounts[1]; 

   deployer.deploy(MyCoin).then(function() {

        return deployer.deploy(MyCrowdsale, openingTime, closingTime, rate, wallet, MyCoin.address); 

   }).then(function() {

        return MyCoin.deployed(); 

   }).then(function(instance) {

        var coin = instance; 

       coin.transferOwnership(MyCrowdsale.address);

    });

};

说明:

  • 上面的合约定义很清楚地表明,众筹需要有Token的地址,因此众筹合约需要在Token部署成功之后进行。

  • 众筹合约需要得到Token的所有权才能进行发行。一开始,Token的owner是部署者(这里是account[0]),因此在众筹合约部署完成之后需要完成Token所有权的移交。

最后一步非常关键,否则会出现类似下面的错误:

Error: VM Exception while processing transaction: revert

   at Object.InvalidResponse ...
...

(6)最后,就是my i-c-o的命令编写了。

const Web3 = require('web3');

const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:9545"));

const contract = require('truffle-contract');

const MyCoin = require('./build/contracts/MyCoin.json');

const myCoin = contract(MyCoin);

myCoin.setProvider(web3.currentProvider);

if (typeof myCoin.currentProvider.sendAsync !== "function") {

    myCoin.currentProvider.sendAsync = function() {

        return myCoin.currentProvider.send.apply(

            myCoin.currentProvider, arguments

        );

    };

}

const MyCrowdsale = require('./build/contracts/MyCrowdsale.json');

const myCrowdsale = contract(MyCrowdsale);

myCrowdsale.setProvider(web3.currentProvider);

if (typeof myCrowdsale.currentProvider.sendAsync !== "function") {

    myCrowdsale.currentProvider.sendAsync = function() {

        return myCrowdsale.currentProvider.send.apply( 

           myCrowdsale.currentProvider, arguments

        );

    };

}

exports.command = 'myico <command> [purchaser] [value]';

exports.describe = 'commands about my ico.';

exports.handler = function(argv) {

    if(argv.command == 'hasClosed') {

        myCrowdsale.deployed().then(function(instance){

            instance.hasClosed().then(function(result){

                console.log(result);

            });

        });

    } else if(argv.command == 'deliver') {

        myCrowdsale.deployed().then(function(instance){

            instance.token().then(function(address){

                instance.sendTransaction({from: argv.purchaser, value: web3.utils.toWei(argv.value.toString(), "ether")}) 

               .then(function(result){

                    console.log('done!');

                }).catch(function(error){ 

                   console.error(error);

                });

            }); 

       });

    } else if(argv.command == 'totalSupply') {

        myCrowdsale.deployed().then(function(instance){

            instance.token().then(function(address){

                let coinInstance = myCoin.at(address);

                coinInstance.totalSupply().then(function(result){

                    console.log(+result);

                });

            }); 

       });

    } else if(argv.command == 'balance') {

        myCrowdsale.deployed().then(function(instance){

            instance.token().then(function(address){

                let coinInstance = myCoin.at(address); 

       coinInstance.balanceOf(argv.purchaser).then(function(balance){

                    console.log(+balance);

                });

            });

        });

    } else {

        console.log('Unknown command!');

    }

}

有了前面simple-data命令代码的说明,这里的代码应该不会有任何难点了。其中的子命令:

  • hasClosed,众筹是否结束

  • totalSupply,众筹中产生的token总量

  • balance,某个账户的代币数

  • deliver,给账户发行代币

到这里,相信大家应该不会再觉得“发币”有什么神秘的了,接下来如何将其应用到业务中,作为业务的催化剂(而不是割韭菜利器)就靠各位的想象力了。(注:本文只分享技术,不做任何项目及投资建议)

本文作者:HiBlock区块链技术布道群-胡键

原文发布于简书

原文链接:

https://www.jianshu.com/p/d78353772029

加微信baobaotalk_com,加入技术布道群

以下是我们的社区介绍,欢迎各种合作、交流、学习:)

image

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值