打开官方网站:https://www.ethereum.org/token#minimum-viable-token ,拷贝官方标准合约代码。
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 : https://github.com/ethereum/mist/releases
安装完成后,通过命令启动 Ethereum Wallet 客户端。
首先使用 geth
启动私有网络
$ geth --datadir ~/privatechain/data0 --networkid 110 --rpc console
然后通过命令启动 Ethereum Wallet
open -a /Applications/Ethereum\ Wallet.app/ --args --rpc /Users/fujinliang/privatechain/data0/geth.ipc
–rpc 的值如何获取?
可以通过启动私链的控制台,查看ipc文件的路径,如下图所示:
部署代币合约,设置代币发行数量、名字
点击 DEPLOY 部署合约
需要在geth
目录下启动挖矿。
miner.start();
查看合约地址和合约Abi
修改 utils/web3helper
创建合约。
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
文件,通过调用合约实现代币转账 ,代码如下:
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
}
}
代币转账和以太币转账方法类似,只有两点不同:
txParms
中 to
为合约的地址txParms
中 需要设置 data
的值代币转账添加路由配置,修改 routers/index.js
,添加如下代码:
const tokenController = require("../controllers/token")
router.post('/token/send', tokenController.sendTokenTransaction)
修改 view/transaction.html
中的代码。
转账币种选择加入 kyb
的选项。
<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">ETHoption>
<option value="kyb">KYBoption>
select>
div>
div>
div>
修改转账 sendTransaction
方法
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
获取代币的信息。
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
}
修改 getAccountByKeystore 和 getAccountByPrivatekey 方法,添加获取代币的代码:
const tokenResult = await module.exports.getTokenBalance(account)
returnResult.data.tokenBalance = tokenResult.balance
returnResult.data.tokenSymbol = tokenResult.symbol
使用 geth
启动私有网络
$ geth --datadir ~/privatechain/data0 --networkid 110 --rpc console
开启本地挖矿
miner.start()
$ cd myWallet
$ node index.js
访问 http://localhost:3000/transaction 查看项目:
https://github.com/didianV5/web3EthWallet/tree/master/006_myWallet