搭建ETH私链并创建ERC20 Token

1、下载ETH钱包

下载 Ethereum Wallet或Mist其一即可,二者功能相同。

2、安装geth

Mac下使用:

1
brew install geth

其余平台参考安装方法

3、编辑创世区块配置文件

genesis.json:(参考示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ludis@MacBook  ~  cd Desktop/ethereum
ludis@MacBook ~/Desktop/ethereum cat > genesis.json
{
"config": {
"chainId": 33,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
"nonce": "0x0000000000000033",
"timestamp": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x8000000",
"difficulty": "0x100",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x3333333333333333333333333333333333333333",
"alloc": {}
}
^c

4、初始化

初始化区块链,并且创建一个文件夹来存储区块数据。由于第一步下载的钱包,其默认的区块数据存储在~/Library/Ethereum目录,所以初始化时,将存储区块数据的文件夹指向该目录,这样钱包就和我们创建的私链绑定起来:

1
geth init genesis.json --datadir ~/Library/Ethereum

启动私链

1
geth --networkid 10 --rpc --rpcapi "admin,debug,eth,miner,net,personal,shh,txpool,web3" rpcaddr "0.0.0.0" --rpccorsdomain "*" --nodiscover  --dev console

终端另开一个窗口,连接本地私链节点,开始挖矿。miner.start(1)传的参数为挖矿所用的线程。

1
2
geth attach 'http://127.0.0.1:8545'
miner.start(1)

如果提示挖矿账户错误,则需要设置挖矿账户为自己的钱包地址:

1
miner.setEtherbase("7df9a875a174b3bc565e6424a0050ebc1b2d1d82")

5、启动钱包

当私链启动后,打开钱包,这是会显示连接到私链成功。点击LAUNCH APPLICATION进入钱包后,创建一个账户即可。

如果打开钱包,显示在同步节点数据,则说明本地私链启动失败,或与钱包绑定失败。

6、创建ERC20 token

创建一个基于ERC20规范的token,合约代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
pragma solidity ^0.4.19;

// ----------------------------------------------------------------------------------------------
// Sample fixed supply token contract
// Enjoy. (c) BokkyPooBah 2017. The MIT Licence.
// ----------------------------------------------------------------------------------------------

// ERC Token Standard #20 Interface
// https://github.com/ethereum/EIPs/issues/20

contract ERC20Interface {
// 获取总的支持量
function totalSupply() constant returns (uint256 totalSupply);

// 获取其他地址的余额
function balanceOf(address _owner) constant returns (uint256 balance);

// 向其他地址发送token
function transfer(address _to, uint256 _value) returns (bool success);

// 从一个地址想另一个地址发送余额
function transferFrom(address _from, address _to, uint256 _value) returns (bool success);

//允许_spender从你的账户转出_value的余额,调用多次会覆盖可用量。某些DEX功能需要此功能
function approve(address _spender, uint256 _value) returns (bool success);

// 返回_spender仍然允许从_owner退出的余额数量
function allowance(address _owner, address _spender) constant returns (uint256 remaining);

// token转移完成后出发
event Transfer(address indexed _from, address indexed _to, uint256 _value);

// approve(address _spender, uint256 _value)调用后触发
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}

//继承接口后的实例

contract FixedSupplyToken is ERC20Interface {
string public constant symbol = "XC"; //单位
string public constant name = "X Coin"; //名称
uint8 public constant decimals = 18; //小数点后的位数
uint256 _totalSupply = 100000000000000000000000000000; //发行总量

// 智能合约的所有者
address public owner;

// 每个账户的余额
mapping(address => uint256) balances;

// 帐户的所有者批准将金额转入另一个帐户。从上面的说明我们可以得知allowed[被转移的账户][转移钱的账户]
mapping(address => mapping (address => uint256)) allowed;

// 只能通过智能合约的所有者才能调用的方法
modifier onlyOwner() {
assert(msg.sender == owner);
_;
}

// 构造函数
function FixedSupplyToken(string _symbol,string _name) public {
owner = msg.sender;
balances[owner] = _totalSupply;
//symbol = _symbol;
//name = _name;
}

function totalSupply() constant public returns (uint256 totalSupply) {
totalSupply = _totalSupply;
}

// 特定账户的代币余额
function balanceOf(address _owner) constant public returns (uint256 balance) {
return balances[_owner];
}

// 转移余额到其他账户
function transfer(address _to, uint256 _amount) public returns (bool success) {
if (balances[msg.sender] >= _amount && _amount > 0 && balances[_to] + _amount > balances[_to]) {
balances[msg.sender] -= _amount;
balances[_to] += _amount;
// balances[交易所地址] += 1;
Transfer(msg.sender, _to, _amount);
return true;
} else {
return false;
}
}

//从一个账户转移到另一个账户,前提是需要有允许转移的余额
function transferFrom(
address _from,
address _to,
uint256 _amount
) public returns (bool success) {
if (balances[_from] >= _amount
&& allowed[_from][msg.sender] >= _amount
&& _amount > 0
&& balances[_to] + _amount > balances[_to]) {
balances[_from] -= _amount;
allowed[_from][msg.sender] -= _amount;
balances[_to] += _amount;
Transfer(_from, _to, _amount);
return true;
} else {
return false;
}
}

//允许账户从当前用户转移余额到那个账户,多次调用会覆盖
function approve(address _spender, uint256 _amount) public returns (bool success) {
allowed[msg.sender][_spender] = _amount;
Approval(msg.sender, _spender, _amount);
return true;
}

//返回被允许转移的余额数量
function allowance(address _owner, address _spender) constant public returns (uint256 remaining) {
return allowed[_owner][_spender];
}
}

7、创建项目

1. 创建一个node项目,来实现合约的编译、部署功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
ludis@MacBook  ~  cd Desktop/contract
ludis@MacBook ~ /Desktop/contract npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (contract)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /Users/ludis/Desktop/contract/package.json:

{
"name": "contract",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}


Is this ok? (yes)

一路回车后初始化了一个node项目,接着安装所需的依赖:

1
ludis@MacBook  ~/Desktop/contract cnpm i -S ganache-cli solc web3

创建以下目录结构:

1
2
3
4
5
6
7
.
├── build
├── compile.js
├── contracts
├── deploy.js
├── node_modules
└── package.json

将第六步的合约命名为FixedSupplyToken.sol保存在contracts文件夹

2. compile.js为编译合约用的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//compile.js 赋值编译智能合约

//require('库名/路径')
const solc = require('solc');
//fs: file system
const fs = require('fs');

const path = require('path');


//1.读取智能合约
//readFileSync 同步读取文件

//__dirname =>返回当前路径
console.log(__dirname);
//resolve路径拼接的方法
const filepath = path.resolve(__dirname,'contracts','FixedSupplyToken.sol');
const contractFile = fs.readFileSync(filepath,'utf8');

// console.log(contractFile);

//通过solc库来编译智能合约

var output = solc.compile(contractFile,1);
// console.log(output);
//output = {contracts:'',errors:'',sourceLiser:''}
const contracts = output.contracts;
// console.log(contracts);
//for in 操作: contract = 对象 中的 属性名
for (let contract in contracts) {
// console.log(contract)
//contracts[contract]
//写文件 fs
// console.log(contract);
let newfilepath = path.resolve(__dirname, 'build', contract.replace(':','') + '.json');
// console.log(newfilepath);
//fs.writeFileSync(目标路径, 写入内容)
//对象, tostring()方法, 返回是一个[object object] 字符串
//JSON.stringify => 将对象装换为字符串
fs.writeFileSync(newfilepath, JSON.stringify(contracts[contract]));
}
//console.log(contracts);

3. deploy.js为部署合约用的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

const ganache = require('ganache-cli');
const Web3 = require('web3');
const fs = require('fs');
const path = require('path');

// web3 小写是实例, Web3 是类
// web3 的构造函数需要传入一个rpc 地址 => provider(http, websocket)
// 内存虚拟节点
// const web3 = new Web3(ganache.provider());
// 直接通过 http://127.0.0.1:8545链接到自己的节点
const web3 = new Web3('http://127.0.0.1:8545');

// 获取编译后的合约代码
const contractpath = path.resolve(__dirname,'build/FixedSupplyToken.json')
// 解析成为json对象
const contract = JSON.parse(fs.readFileSync(contractpath,'utf8'));

// 构建一个智能合约对象

let myContract = new web3.eth.Contract(JSON.parse(contract.interface));

web3.eth.getAccounts()
.then(accounts => {
// web3.personal.unlockAccount(eth.accounts[0],"asdqwe123", 15000)
myContract.deploy({
data: '0x' + contract.bytecode,// bytecode
arguments: ["MT", 'My TOKEN'] // 合约构造函数参数s
})
.send({
from:accounts[0],
gas: 1500000, // gas 消耗最大值
gasPrice: web3.utils.toWei("0.0000002", "ether")
})
.then(d => { // 部署合约的返回结果是实例对象
d.methods.name().call().then(r => {
console.log(r);
});
// 合约方法调用
// 合约实例.methods.方法名(方法参数).call()
d.methods.totalSupply().call().then(r => {
console.log(r);
})
// console.log(d)
})
})

8、编译部署

编译: node compile.js,编译成功会在build文件夹生成编译完的json文件

部署: node deploy.js,部署时会提示钱包账户解锁问题,只需终端执行web3.personal.unlockAccount(eth.accounts[0],"password", 15000),然后再部署即可。

部署成功后终端挖矿页面会显示区块的打包信息,当合约部署成功时会显示合约地址。然后把合约地址添加到钱包的contracts->token中,就会显示该合约下的token数量。可以新建多个用户转账测试。

Author

Ludis

Posted on

2018-03-12

Updated on

2018-03-12

Licensed under

Comments