DTeam 技术日志
这周,让我们跟随着一些简单的例子从近处看看以太坊DApp的开发细节。当然啦,本文也会涉及到以太坊中的一个热门场景:“ICO”,满足一下各位苦逼的开发当一回大佬的愿望,;)
本文用到的开发工具:
文章中创建的项目为一个“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的开发,实在兴趣不大。
那么,让我们先来准备工程的架子:
执行完成后,cli工程需要基本环境就都具备了。之后,在项目的工程根下创建app.js,它将作为整个工程的入口。并且工程采用Command Module的方式组织。
app.js的内容如下:
#!/usr/bin/env node
require('yargs')
.command(require('./simple-data.js'))
.command(require('./myico.js'))
.help()
.argv
其中的两条命令以module方式组织,分别对应:
simple-data命令展示了一个简单的前端和合约交互的例子,为编写复杂交互提供了参考。它的整个过程如下:
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;
}
}
const SimpleData = artifacts.require("./SimpleData.sol");
module.exports = function(deployer) {
deployer.deploy(SimpleData);
};
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!');
}
}
说明:
编译部署之后,就可以简单的试用了(先给app.js可执行权限):
接下来,就到了最让人期待的时刻:发币,更准确的说是基于ERC 20的代币发放。因为以太坊中各个币种有不同的含义和用途,比如另一个常见的币种:ERC 721,它发行的每个token都是独一无二不可互换的,比如以太猫。
关于发币,究其本质就是实现特定的合约接口。从开发的投入产出比来讲,我建议采用OpenZeppelin:
以下是使用OpenZeppelin开发ico的过程:
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; // 位数
}
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) {
}
}
几行代码就完成了核心合约的开发,这要归功于咱们选的框架,;)
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);
});
};
说明:
最后一步非常关键,否则会出现类似下面的错误:
Error: VM Exception while processing transaction: revert
at Object.InvalidResponse ...
...
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命令代码的说明,这里的代码应该不会有任何难点了。其中的子命令:
到这里,相信大家应该不会再觉得“发币”有什么神秘的了,接下来如何将其应用到业务中,作为业务的催化剂(而不是割韭菜利器)就靠各位的想象力了。