1. 程式人生 > >006 以太坊錢包開發-發放token、token轉賬

006 以太坊錢包開發-發放token、token轉賬

私鏈發放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

-w991

安裝完成後,通過命令啟動 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檔案的路徑,如下圖所示:

-w570

部署代幣合約

部署代幣合約,設定代幣發行數量、名字

點選 DEPLOY 部署合約

需要在geth 目錄下啟動挖礦。

miner.start();

檢視合約地址和合約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": 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
    }
}

代幣轉賬和以太幣轉賬方法類似,只有兩點不同:

  • 轉賬引數 txParmsto 為合約的地址
  • 轉賬引數 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 == 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
    }

修改 getAccountByKeystoregetAccountByPrivatekey 方法,新增獲取代幣的程式碼:

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

原始碼下載

關注我

公眾號