美文网首页
以太坊开发(二十九)[升级版]使用Web3.js+Node.js

以太坊开发(二十九)[升级版]使用Web3.js+Node.js

作者: yuyangray | 来源:发表于2018-10-29 16:31 被阅读199次

    1. 前言

    之前的文章介绍过使用Web3.js+Node.js封装成接口以供钱包管理/查询/转账。但是有一些问题:

    • 代币转账可能出现超时失败

    • 以太币/代币转账接口调用成功后,返回的交易hash,并不能保证交易已成功,还需要再次去查询交易状态

    • 以太币转账接口未判断转账者是否有足够的以太币。代币转账接口未判断转账者是否有足够的代币

    • 转账前没有预估gas

    • 以太币转账,之前必须传入带小数位的金额。例如转账1ether,传入的参数为1000000000000000000

    • 代币转账,之前必须先去查询该代币小数位,然后换算成带小数位的代币金额。例如转账1某token,传入的参数为1000000000000000000

    • 代币转账,之前必须将转账方法名,转账人地址,金额,转换为十六进制,再拼接为data

    • 根据passwordprivateKey返回keystore。之前必须将privateKey手动加上0x前缀再进行传参,否则将返回错误的keystore

    针对这些问题,对代码进行了修改:

    • 修复了代币转账可能出现超时的问题

    • 调用转账接口后,只要返回交易hash,则此交易必定为success状态,不需要再去查询交易状态。耗时取决于当前以太坊网络状况,经测试在15-60秒左右

    • 自动预估gas,移除了gasLimit参数

    • 以太币转账。现在自动判断转账方是否有足够的以太币

    • 以太币转账。现在传入的以太币单位为ether不需要再加上18个0的小数位

    • 代币转账。现在自动判断转账方是否有足够的代币

    • 代币转账。不需要先去获取代币小数位,现在直接传入不带小数位的代币金额。例如,之前某个代币小数位为8,转账1token,则传入100000000。现在直接传入1

    • 根据passwordprivateKey返回keystore。现在直接传入不带0x前缀的私钥

    2. 关键代码

    不明白可以看注释

    // ======================Tools==========================
    // =====================================================
    // =====================================================
    
    
    /**
     * 创建钱包
     * 
     * 参数:1.password: 钱包密码
     * 
     * 返回:1.address: 账号地址
     *     2.privateKey: 私钥
     *     3.keystore: keystore
     *   
     */
    router.get('/eth/account/createWallet', async(ctx, next) => {
    
        if (typeof(ctx.request.query.password) == 'undefined' || 
            ctx.request.query.password == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: password.',
            })
            return
        } 
    
        try {
            let account = web3.eth.accounts.create()
    
            let response = web3.eth.accounts.encrypt(account.privateKey, 
            ctx.request.query.password)
    
            ctx.body = await Promise.resolve({
                code: 0,
                address: account.address,
                privateKey: account.privateKey.substr(2),
                keystore: response,
                message: 'Success',
            })
        } catch (error) {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: error.stack,
            })
        }
    })
    
    
    /**
     * 根据password和keystore返回privateKey
     *
     * 参数:1.password: 钱包密码
     *     2.keystore: keystore
     * 
     * 返回:1.privateKey: 私钥
     *
     */
    router.get('/eth/account/getPrivateKey', async(ctx, next) => {
        
    
        let { password, keystore } = ctx.request.query
    
        if (typeof(password) == 'undefined' || 
            password == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: password.',
            })
            return
        } 
    
        if (typeof(keystore) == 'undefined' || 
            keystore == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: keystore.',
            })
            return
        } 
    
        try {
            let response = web3.eth.accounts.decrypt(
                keystore, 
                password)
    
            ctx.body = await Promise.resolve({
                code: 0,
                data: response.privateKey.substr(2),
                message: 'Success',
            })
        } catch (error) {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: error.stack,
            })
        }
    })
    
    
    /**
     * 根据password和privateKey返回keystore
     *
     * 参数:1.password: 钱包密码
     *     2.privateKey: 私钥
     * 
     * 返回:1.keystore: keystore 
     *
     */
    router.get('/eth/account/getKeystore', async(ctx, next) => {
        
        let { password, privateKey } = ctx.request.query
    
        if (typeof(password) == 'undefined' || 
            password == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: password.',
            })
            return
        } 
    
        if (typeof(privateKey) == 'undefined' || 
            privateKey == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: privateKey.',
            })
            return
        } 
    
        try {
            let response = web3.eth.accounts.encrypt("0x" + privateKey, 
                password)
    
            ctx.body = await Promise.resolve({
                code: 0,
                data: response,
                message: 'Success',
            })
        } catch (error) {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: error.stack,
            })
        }
    })
    
    /**
     * 获得当前最新区块高度
     *
     * 参数:无
     * 
     * 返回: 1.data: 最新区块高度
     */
    router.get('/eth/getBlockNumber', async(ctx, next) => {
        
        try {
            let response = await web3.eth.getBlockNumber()
            ctx.body = await Promise.resolve({
                code: 0,
                data: response,
                message: 'Success',
            })
        } catch (error) {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: error.stack,
            })
        }
    })
    
    /**
     * 根据交易hash查询交易详情
     *
     * 参数:1.hash: 交易哈希
     * 
     * 返回:1.data: 交易详情
     *   
     */
    router.get('/eth/getTransaction', async(ctx, next) => {
    
        if (typeof(ctx.request.query.hash) == 'undefined' || 
            ctx.request.query.hash == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: hash.',
            })
            return
        } 
    
        try {
            let response = await web3.eth.getTransaction(ctx.request.query.hash)
            ctx.body = await Promise.resolve({
                code: 0,
                data: response,
                message: 'Success',
            })
        } catch (error) {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: error.stack,
            })
        }
    })
    
    /**
     * 获取合约ABI
     *
     * 参数:1.contractAddress: 合约地址
     * 
     * 返回:1.data: 合约ABI
     *   
     */
    router.get('/token/getAbi', async(ctx, next) => {
    
        if (typeof(ctx.request.query.contractAddress) == 'undefined' || 
            ctx.request.query.contractAddress == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: contractAddress.',
            })
            return
        } 
    
        try {
            let response = await tools.getAbi(ctx.request.query.contractAddress)
            ctx.body = await Promise.resolve({
                code: 0,
                data: eval(response.data.result),
                message: 'Success',
            })
        } catch (error) {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: error.stack,
            })
        }
    })
    
    
    
    // ======================Ether==========================
    // =====================================================
    // =====================================================
    
    /**
     * ETH余额查询
     *
     * 参数:1.address: 查询地址
     * 
     * 返回:1.data: ETH余额,单位为Ether
     *   
     */
    router.get('/eth/getBalance', async(ctx, next) => {
    
        if (typeof(ctx.request.query.address) == 'undefined' || 
            ctx.request.query.address == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: address.',
            })
            return
        }
    
        try {
            let balance = await web3.eth.getBalance(ctx.request.query.address)
            ctx.body = await Promise.resolve({
                code: 0,
                data: web3.utils.fromWei(balance, 'ether'),
                message: 'Success',
            })
        } catch (error) {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: error.stack,
            })
        }
    })
    
    /**
     * ETH转账
     *
     * 1.自动检查必填参数是否传入,否则会提示某项参数缺失
     * 2.gasPrice为可选参数。如果未传入,此接口默认取当前网络平均值。建议不传,使用默认值。
     *   gasPrice单位为Gwei
     * 3.传入的ETH单位为Ether,此接口会自动进行转换为Wei。例如,转账1eth,
     *   则传入1,而非1000000000000000000
     * 4.此接口会检测转出方是否有足够ETH进行转账,否则会提示ETH不足
     * 5.当接口返回交易hash时,此交易的状态即为success,不需要再通过查询获取交易状态
     * 6.此接口会在交易状态为success时才返回交易hash,耗时取决于当前以太坊网络状况,
     *   经测试在25-60秒左右。未出现过超时问题
     *
     *
     * 参数:1.transferAddress: 转账方地址
     *     2.receiverAddress: 接收方地址
     *     3.privateKey: 私钥
     *     4.num: eth数量
     *     5.gasPrice: (可选)燃料费单价
     * 
     * 返回:1.data: 交易成功的hash
     *   
     */
    router.get('/eth/transfer', async(ctx, next) => {
    
        let { transferAddress, receiverAddress, privateKey, 
            num, gasPrice } = ctx.request.query
    
    
        if (typeof(transferAddress) == 'undefined' || 
            transferAddress == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: transferAddress.',
            })
            return
        }
    
        if (typeof(receiverAddress) == 'undefined' || 
            receiverAddress == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: receiverAddress.',
            })
            return
        }
    
        if (typeof(privateKey) == 'undefined' || 
            privateKey == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: privateKey.',
            })
            return
        }
    
        if (typeof(num) == 'undefined' || 
            num == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: num.',
            })
            return
        }
    
    
         // 如果没有传入gasPrice, 默认调用web3接口获取最近区块的gasPrice的平均值
        if (typeof(gasPrice) == 'undefined' || 
            gasPrice == '') {
            gasPrice = await web3.eth.getGasPrice()
        } else {
            // 传值是传入的单位为gwei,需要转为wei
            gasPrice = web3.utils.toWei(gasPrice, 'gwei')
        }
    
        let amount = await web3.utils.toWei(num)
    
        console.log("transferAddress: " + transferAddress + 
            " receiverAddress: " + receiverAddress + 
            " privateKey: " + privateKey + 
            " num: " + num + 
            " gasPrice: " + gasPrice);
    
    
        // 判断转账方是否有足够的以太币转账
        let balance = await web3.eth.getBalance(transferAddress)
    
        console.log("balance: " + balance)
        console.log("amount: " + amount)
    
        if(parseInt(balance) < parseInt(amount)){
            console.log("balance < amount")
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Not enough ETH.',
            })
            return
        }
    
        let nonce = await web3.eth.getTransactionCount(transferAddress)
    
        console.log("nonce: " + nonce)
    
        
        var rawTx = {
            from:transferAddress,
            nonce: nonce,
            gasPrice: gasPrice,
            to: receiverAddress,
            value: amount,
            data: '0x00'
        }
        
        let gas = await web3.eth.estimateGas(rawTx)
        rawTx.gas = gas
    
        console.log("gas: " + gas)
    
        var tx = new Tx(rawTx)
    
        var _privateKey = new Buffer.from(privateKey, 'hex')
    
        tx.sign(_privateKey)
    
        var serializedTx = tx.serialize().toString('hex')
    
        await web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'), async function(err, data) {
                console.log(err)
                console.log(data)
    
                if (err) {
                    ctx.body = await Promise.resolve({
                    code: 1,
                    data: {},
                    message: 'Fail',
                    })
                }
            })
            .then(async function(data) {
                console.log(data)
                if (data) {
                    ctx.body = await Promise.resolve({
                    code: 0,
                    data: data.transactionHash,
                    message: 'Success',
                    })
                } else {
                    ctx.body = await Promise.resolve({
                    code: 1,
                    data: {},
                    message: 'Fail',
                })
            }
        })
    })
    
    // ======================Token==========================
    // =====================================================
    // =====================================================
    
    
    /**
     * 获得代币名称
     *
     * 参数:1.contractAddress: 代币合约地址
     * 
     * 返回:1.data: 代币名称
     *   
     */
    router.get('/token/getName', async(ctx, next) => {
      
        let { contractAddress } = ctx.request.query
    
        if (typeof(contractAddress) == 'undefined' || 
            contractAddress == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: contractAddress.',
            })
            return
        } 
    
        let abi = await tools.getAbi(contractAddress)
        let contractAbi = eval(abi.data.result)
        let contract = new web3.eth.Contract(contractAbi, contractAddress)
    
        console.log("abi: " + abi +
            "contractAbi: " + contractAbi + 
            " contract: " + contract);
    
        try {
            let response = await contract.methods.name().call()
            ctx.body = await Promise.resolve({
                code: 0,
                data: response,
                message: 'Success',
            })
        } catch (error) {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: error.stack,
            })
        }    
    })
    
    /**
     * 获得代币符号
     *
     * 参数:1.contractAddress: 代币合约地址
     * 
     * 返回:1.data: 代币符号
     *   
     */
    router.get('/token/getSymbol', async(ctx, next) => {
      
        let { contractAddress } = ctx.request.query
    
        if (typeof(contractAddress) == 'undefined' || 
            contractAddress == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: contractAddress.',
            })
            return
        } 
    
        let abi = await tools.getAbi(contractAddress)
        let contractAbi = eval(abi.data.result)
        let contract = new web3.eth.Contract(contractAbi, contractAddress)
    
        console.log("abi: " + abi +
            "contractAbi: " + contractAbi + 
            " contract: " + contract);
    
        try {
            let response = await contract.methods.symbol().call()
            ctx.body = await Promise.resolve({
                code: 0,
                data: response,
                message: 'Success',
            })
        } catch (error) {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: error.stack,
            })
        }    
    })
    
    /**
     * 获得代币小数位
     *
     * 参数:1.contractAddress: 代币合约地址
     * 
     * 返回:1.data: 代币小数位
     *   
     */
    router.get('/token/getDecimals', async(ctx, next) => {
      
        let { contractAddress } = ctx.request.query
    
        if (typeof(contractAddress) == 'undefined' || 
            contractAddress == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: contractAddress.',
            })
            return
        } 
    
        let abi = await tools.getAbi(contractAddress)
        let contractAbi = eval(abi.data.result)
        let contract = new web3.eth.Contract(contractAbi, contractAddress)
    
        console.log("abi: " + abi +
            "contractAbi: " + contractAbi + 
            " contract: " + contract);
    
        try {
            let response = await contract.methods.decimals().call()
            ctx.body = await Promise.resolve({
                code: 0,
                data: response,
                message: 'Success',
            })
        } catch (error) {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: error.stack,
            })
        }    
    })
    
     /**
     * token余额查询
     *
     * 参数:1.contractAddress: 代币合约地址
     *     2.address: 查询地址
     * 
     * 返回:1.data: 代币余额
     *   
     */
    router.get('/token/getBalance', async(ctx, next) => {
    
        let { contractAddress, address } = ctx.request.query
    
        if (typeof(contractAddress) == 'undefined' || 
            contractAddress == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: contractAddress.',
            })
            return
        }
    
        if (typeof(address) == 'undefined' || 
            address == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: address.',
            })
            return
        }
    
        let abi = await tools.getAbi(contractAddress)
        let contractAbi = eval(abi.data.result)
        let contract = new web3.eth.Contract(contractAbi, contractAddress)
    
        console.log("abi: " + abi +
            "contractAbi: " + contractAbi + 
            " contract: " + contract);
    
        let decimals = await contract.methods.decimals().call()
        let symbol = await contract.methods.symbol().call()
    
        console.log("decimals: " + decimals +
            " symbol: " + symbol);
    
        try {
            let balance = await contract.methods.balanceOf(address).call()
            ctx.body = await Promise.resolve({
                code: 0,
                data: balance / Math.pow(10, decimals),
                message: 'Success',
            })
        } catch (error) {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: error.stack,
            })
        }
    })
    
    /**
     * Token转账
     *
     * 1.自动检查必填参数是否传入,否则会提示某项参数缺失
     * 2.gasPrice为可选参数。如果未传入,此接口默认取当前网络平均值。建议不传,使用默认值。
     *   gasPrice单位为Gwei
     * 3.传入的代币金额为不带小数位的金额,此接口会自动进行转换。例如,转账1token,
     *   则传入1,而非1000000000000000000
     * 4.此接口会检测转出方是否有足够token进行转账,否则会提示token不足
     * 5.当接口返回交易hash时,此交易的状态即为success,不需要再通过查询获取交易状态
     * 6.此接口会在交易状态为success时才返回交易hash,耗时取决于当前以太坊网络状况,
     *   经测试在25-60秒左右。未出现过超时问题
     *
     *
     * 参数:1.contractAddress: 代币合约地址
     *     2.transferAddress: 转账方地址
     *     3.receiverAddress: 接收方地址
     *     4.privateKey: 私钥
     *     5.num: 代币数量
     *     6.gasPrice: (可选)燃料费单价
     * 
     * 返回:1.data: 交易成功的hash
     *   
     */
    router.get('/token/transfer', async(ctx, next) => {
        let { contractAddress, transferAddress, receiverAddress, privateKey, 
            num, gasPrice } = ctx.request.query
    
        if (typeof(contractAddress) == 'undefined' || 
            contractAddress == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: contractAddress.',
            })
            return
        }
    
        if (typeof(transferAddress) == 'undefined' || 
            transferAddress == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: transferAddress.',
            })
            return
        }
    
        if (typeof(receiverAddress) == 'undefined' || 
            receiverAddress == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: receiverAddress.',
            })
            return
        }
    
        if (typeof(privateKey) == 'undefined' || 
            privateKey == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: privateKey.',
            })
            return
        }
    
        if (typeof(num) == 'undefined' || 
            num == '') {
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Missing parameter: num.',
            })
            return
        }
    
    
         // 如果没有传入gasPrice, 默认调用web3接口获取最近区块的gasPrice的平均值
        if (typeof(gasPrice) == 'undefined' || 
            gasPrice == '') {
            gasPrice = await web3.eth.getGasPrice()
        } else {
            // 传值是传入的单位为gwei,需要转为wei
            gasPrice = web3.utils.toWei(gasPrice, 'gwei')
        }
    
        console.log("contractAddress: " + contractAddress +
            " transferAddress: " + transferAddress + 
            " receiverAddress: " + receiverAddress + 
            " privateKey: " + privateKey + 
            " num: " + num + 
            " gasPrice: " + gasPrice);
    
        let abi = await tools.getAbi(contractAddress)
        let contractAbi = eval(abi.data.result)
        let contract = new web3.eth.Contract(contractAbi, contractAddress)
    
        console.log("abi: " + abi +
            "contractAbi: " + contractAbi + 
            " contract: " + contract);
    
        let decimals = await contract.methods.decimals().call()
        let amount = num * Math.pow(10, decimals)
        let symbol = await contract.methods.symbol().call()
    
         console.log("decimals: " + decimals +
            " symbol: " + symbol);
    
        let balance = await contract.methods.balanceOf(transferAddress).call()
    
        console.log("balance: " + balance)
        console.log("amount: " + amount)
    
        console.log(parseInt(balance) < parseInt(amount))
    
        if (parseInt(balance) < parseInt(amount)) {
            console.log("balance < amount")
            ctx.body = await Promise.resolve({
                code: 1,
                data: {},
                message: 'Not enough ' + symbol + '.',
            })
            return
        }
    
        console.log("receiverAddress: " + receiverAddress)
        console.log("amount: " + amount)
    
        let tokenTransferData = await contract.methods.transfer(receiverAddress, 
            web3.utils.toHex(amount)).encodeABI()
    
        console.log("tokenTransferData: " + tokenTransferData)
    
        let nonce = await web3.eth.getTransactionCount(transferAddress)
    
        console.log("nonce: " + nonce)
    
        var rawTx = {
            from: transferAddress,
            nonce: nonce,
            gasPrice: gasPrice,
            to: contractAddress,
            data: tokenTransferData
        }
    
        let gas = await web3.eth.estimateGas(rawTx)
        rawTx.gas = gas
    
        console.log("gas: " + gas)
    
        var tx = new Tx(rawTx)
    
        var _privateKey = new Buffer.from(privateKey, 'hex')
    
        tx.sign(_privateKey)
    
        var serializedTx = tx.serialize().toString('hex')
    
        await web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'), async function(err, data) {
                console.log(err)
                console.log(data)
    
                if (err) {
                    ctx.body = await Promise.resolve({
                    code: 1,
                    data: {},
                    message: 'Fail',
                    })
                }
            })
            .then(async function(data) {
                console.log(data)
                if (data) {
                    ctx.body = await Promise.resolve({
                    code: 0,
                    data: data.transactionHash,
                    message: 'Success',
                    })
                } else {
                    ctx.body = await Promise.resolve({
                    code: 1,
                    data: {},
                    message: 'Fail',
                })
            }
        })
    })
    

    相关文章

      网友评论

          本文标题:以太坊开发(二十九)[升级版]使用Web3.js+Node.js

          本文链接:https://www.haomeiwen.com/subject/qsibtqtx.html