私鍊發放token
編寫代币合約代碼
打開官方網站:https://www.ethereum.org/token#minimum-viable-token ,拷貝官方标準合約代碼。
pragma solidity ^;
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 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 * ** 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 != );
// 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
安裝完成後,通過指令啟動 Ethereum Wallet 用戶端。
首先使用
geth
啟動私有網絡
然後通過指令啟動 Ethereum Wallet
–rpc 的值如何擷取?
可以通過啟動私鍊的控制台,檢視ipc檔案的路徑,如下圖所示:
部署代币合約
部署代币合約,設定代币發行數量、名字
點選 DEPLOY 部署合約
需要在
geth
目錄下啟動挖礦。
檢視合約位址和合約Abi
web3 調用代币轉賬
調用合約實作代币轉賬
修改
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": , "typeShort": "uint", "bits": "256", "displayName": "initial Supply", "template": "elements_input_uint", "value": "10000000" }, { "name": "tokenName", "type": "string", "index": , "typeShort": "string", "bits": "", "displayName": "token Name", "template": "elements_input_string", "value": "kongyixueyuan" }, { "name": "tokenSymbol", "type": "string", "index": , "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: ,
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(,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">ETH</option>
<option value="kyb">KYB</option>
</select>
</div>
</div>
</div>
修改轉賬
sendTransaction
方法
if (tokenType == "kyb"){
$.post("/token/send",params,function(res){
if (res.code == ) {
alert("交易成功!")
$("#txHashDiv").show()
$("#txHash").html(res.data.hash)
} else {
alert("交易失敗!"+res.data.error)
}
})
}
擷取賬戶中的代币金額
修改
controllers/account.js
添加
getTokenBalance
擷取代币的資訊。
async getTokenBalance(account){
let returnResult = {
balance: ,
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(,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
啟動私有網絡
開啟本地挖礦
啟動項目
$ cd myWallet
$ node index.js
通路 http://localhost:3000/transaction 檢視項目:
源碼下載下傳
https://github.com/didianV5/web3EthWallet/tree/master/006_myWallet