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

钱包 Mar 19, 2020

私链发放token

编写代币合约代码

打开官方网站: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

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

-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

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

源码下载

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

恭喜! 你已经成功订阅了.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.