006 以太坊钱包开发-发放token、token转账

私链发放token

编写代币合约代码

打开官方网站:https://www.ethereum.org/token#minimum-viable-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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
pragma solidity ^0.4.16;

interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) external; }

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 generates a public event on the blockchain that will notify clients
event Approval(address indexed _owner, address indexed _spender, uint256 _value);

// This notifies clients about the amount burnt
event Burn(address indexed from, uint256 value);

/**
* Constructor function
*
* Initializes contract with initial supply tokens to the creator of the contract
*/
constructor(
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;
emit 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 returns (bool success) {
_transfer(msg.sender, _to, _value);
return true;
}

/**
* 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;
emit Approval(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
emit 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
emit Burn(_from, _value);
return true;
}
}

name : 代币名称
symbol: 代币符号
decimals: 代币小数点位数,代币的最小单位, 18表示我们可以拥有 .0000000000000000001单位个代币。
totalSupply() : 发行代币总量。
balanceOf(): 查看对应账号的代币余额。
transfer(): 实现代币交易,用于给用户发送代币(从我们的账户里)。
transferFrom(): 实现代币用户之间的交易。
allowance(): 控制代币的交易,如可交易账号及资产。
approve(): 允许用户可花费的代币数。

下载 Ethereum Wallet

下载Ethereum Wallet : https://github.com/ethereum/mist/releases

-w991

安装完成后,通过命令启动 Ethereum Wallet 客户端。

首先使用 geth 启动私有网络

1
$ geth --datadir ~/privatechain/data0 --networkid 110  --rpc console

然后通过命令启动 Ethereum Wallet

1
open -a /Applications/Ethereum\ Wallet.app/ --args --rpc /Users/fujinliang/privatechain/data0/geth.ipc

–rpc 的值如何获取?

可以通过启动私链的控制台,查看ipc文件的路径,如下图所示:

-w570

部署代币合约

部署代币合约,设置代币发行数量、名字

点击 DEPLOY 部署合约

需要在geth 目录下启动挖矿。

1
miner.start();

查看合约地址和合约Abi

web3 调用代币转账

调用合约实现代币转账

修改 utils/web3helper 创建合约。

1
2
3
4
5
6
7
8
9
10
11
12
getContract() {

const web3 = module.exports.getWeb3()
// 定义合约abi
const contractAbi = [ { "constant": true, "inputs": [], "name": "name", "outputs": [ { "name": "", "type": "string", "value": "kongyixueyuan" } ], "payable": false, "stateMutability": "view", "type": "function", "signature": "0x06fdde03" }, { "constant": false, "inputs": [ { "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "approve", "outputs": [ { "name": "success", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function", "signature": "0x095ea7b3" }, { "constant": true, "inputs": [], "name": "totalSupply", "outputs": [ { "name": "", "type": "uint256", "value": "10000000000000000000000000" } ], "payable": false, "stateMutability": "view", "type": "function", "signature": "0x18160ddd" }, { "constant": false, "inputs": [ { "name": "_from", "type": "address" }, { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "name": "success", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function", "signature": "0x23b872dd" }, { "constant": true, "inputs": [], "name": "decimals", "outputs": [ { "name": "", "type": "uint8", "value": "18" } ], "payable": false, "stateMutability": "view", "type": "function", "signature": "0x313ce567" }, { "constant": false, "inputs": [ { "name": "_value", "type": "uint256" } ], "name": "burn", "outputs": [ { "name": "success", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function", "signature": "0x42966c68" }, { "constant": true, "inputs": [ { "name": "", "type": "address" } ], "name": "balanceOf", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function", "signature": "0x70a08231" }, { "constant": false, "inputs": [ { "name": "_from", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "burnFrom", "outputs": [ { "name": "success", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function", "signature": "0x79cc6790" }, { "constant": true, "inputs": [], "name": "symbol", "outputs": [ { "name": "", "type": "string", "value": "kyb" } ], "payable": false, "stateMutability": "view", "type": "function", "signature": "0x95d89b41" }, { "constant": false, "inputs": [ { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transfer", "outputs": [ { "name": "success", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function", "signature": "0xa9059cbb" }, { "constant": false, "inputs": [ { "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" }, { "name": "_extraData", "type": "bytes" } ], "name": "approveAndCall", "outputs": [ { "name": "success", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function", "signature": "0xcae9ca51" }, { "constant": true, "inputs": [ { "name": "", "type": "address" }, { "name": "", "type": "address" } ], "name": "allowance", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function", "signature": "0xdd62ed3e" }, { "inputs": [ { "name": "initialSupply", "type": "uint256", "index": 0, "typeShort": "uint", "bits": "256", "displayName": "initial Supply", "template": "elements_input_uint", "value": "10000000" }, { "name": "tokenName", "type": "string", "index": 1, "typeShort": "string", "bits": "", "displayName": "token Name", "template": "elements_input_string", "value": "kongyixueyuan" }, { "name": "tokenSymbol", "type": "string", "index": 2, "typeShort": "string", "bits": "", "displayName": "token Symbol", "template": "elements_input_string", "value": "kyb" } ], "payable": false, "stateMutability": "nonpayable", "type": "constructor", "signature": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "from", "type": "address" }, { "indexed": true, "name": "to", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event", "signature": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "_owner", "type": "address" }, { "indexed": true, "name": "_spender", "type": "address" }, { "indexed": false, "name": "_value", "type": "uint256" } ], "name": "Approval", "type": "event", "signature": "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "from", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Burn", "type": "event", "signature": "0xcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5" } ]
// 合约地址
const contractAddress = "0xF2B6b76f1d0Ea4dC8ca543765640224819af3aA2"

const myContract = new web3.eth.Contract(contractAbi,contractAddress)

return myContract
}

在 controllers 中 新建 token.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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
const web3 = require("../utils/web3helper").getWeb3()
const BigNumber = require('bignumber.js');
const myContract = require("../utils/web3helper").getContract()

module.exports = {

async sendTokenTransaction (ctx) {
let returnResult = {
code: 0,
msg: '成功!',
data: {}
}

const data = ctx.request.body

const currentAccount = data.currAccount
const privateKey = data.privateKey
const reciptAccount = data.reciptAccount
const txValue = data.txValue
// 获取指定账户地址的交易数
let nonce = await web3.eth.getTransactionCount(currentAccount);

// 获取设置的位数
const decimals = await myContract.methods.decimals().call()
// 将输入的值 转为 最小单位的值
const value = BigNumber(txValue * Math.pow(10,decimals));
const txData = myContract.methods.transfer(reciptAccount, value).encodeABI();

// 获取当前gasprice
let gasPrice = await web3.eth.getGasPrice();

// 以太币转账参数
let txParms = {
from: currentAccount,
// 合约地址
to: myContract.options.address,
nonce: nonce,
gasPrice: gasPrice,
data: txData // 当使用代币转账或者合约调用时
}

// 获取一下预估gas
let gas = await web3.eth.estimateGas(txParms);
txParms.gas = gas;
// 用密钥对账单进行签名
let signTx = await web3.eth.accounts.signTransaction(txParms,privateKey)

// 将签过名的账单进行发送
try {
await web3.eth.sendSignedTransaction(signTx.rawTransaction, function(error, hash){
if (!error) {
returnResult.data.hash = hash
} else {
returnResult.code = "101"
returnResult.msg = "失败!"
returnResult.data.error = error.message

}
})
} catch (error) {
console.log(error)
}

ctx.body = returnResult
}
}

代币转账和以太币转账方法类似,只有两点不同:

  • 转账参数 txParmsto 为合约的地址
  • 转账参数 txParms 中 需要设置 data 的值

修改路由配置

代币转账添加路由配置,修改 routers/index.js,添加如下代码:

1
2
3
const tokenController = require("../controllers/token")

router.post('/token/send', tokenController.sendTokenTransaction)

修改转账页面

修改 view/transaction.html 中的代码。

转账币种选择加入 kyb的选项。

1
2
3
4
5
6
7
8
9
10
11
12
<div class="form-group">
<label for="txValue">转账金额</label>
<div class="input-group mb-3">
<input type="text" class="form-control" id="txValue" placeholder="">
<div class="input-group-append">
<select id="tokenType">
<option value="eth">ETH</option>
<option value="kyb">KYB</option>
</select>
</div>
</div>
</div>

修改转账 sendTransaction 方法

1
2
3
4
5
6
7
8
9
10
11
if (tokenType == "kyb"){
$.post("/token/send",params,function(res){
if (res.code == 0) {
alert("交易成功!")
$("#txHashDiv").show()
$("#txHash").html(res.data.hash)
} else {
alert("交易失败!"+res.data.error)
}
})
}

获取账户中的代币金额

修改 controllers/account.js 添加 getTokenBalance 获取代币的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
async getTokenBalance(account){

let returnResult = {
balance: 0,
symbol: 'kongyixueyuan'
}

// 代币小数点位数
const decimals = await myContract.methods.decimals().call()
// 代币符号
const symbol = await myContract.methods.symbol().call()
const tokenBalance = await myContract.methods.balanceOf(account.address).call()
const tokenBalanceNum = tokenBalance / Math.pow(10,decimals)

returnResult.balance = tokenBalanceNum
returnResult.symbol = symbol

return returnResult
}

修改 getAccountByKeystoregetAccountByPrivatekey 方法,添加获取代币的代码:

1
2
3
const tokenResult = await module.exports.getTokenBalance(account)
returnResult.data.tokenBalance = tokenResult.balance
returnResult.data.tokenSymbol = tokenResult.symbol

项目运行

启动私链网络

使用 geth 启动私有网络

1
$ geth --datadir ~/privatechain/data0 --networkid 110  --rpc console

开启本地挖矿

1
2
3
4
miner.start()
```

### 启动项目

$ cd myWallet
$ node index.js
`

访问 http://localhost:3000/transaction 查看项目:

源码下载

https://github.com/didianV5/web3EthWallet/tree/master/006_myWallet

谢谢你请我吃糖果