區塊鏈100講:今天想要教你們發個幣~

image

本講將經過一些簡單的例子從近處看以太坊DApp的開發細節。偷偷告訴你,本文會涉及到以太坊中的一個熱門場景:「發幣」,知足一下各位苦逼的開發當一回大佬的願望 ;)前端

1

背景

本文用到的開發工具:node

  • Nodegit

  • Trufflegithub

相關的包:web

  • yargs,cli庫npm

  • web3,json-rpc抽象json

  • truffle-contract,合約抽象安全

  • openzeppelin-solidity,安全合約庫微信

文章中建立的項目爲一個「node + truffle」工程,對外提供cli。這個cli暴露了兩條命令:app

$./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 <action> [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

相關文章
相關標籤/搜索