1. 前言
之前的文章介绍过使用Web3.js
+Node.js
封装成接口以供钱包管理/查询/转账。但是有一些问题:
-
代币转账可能出现超时失败
-
以太币/代币转账接口调用成功后,返回的交易
hash
,并不能保证交易已成功,还需要再次去查询交易状态 -
以太币转账接口未判断转账者是否有足够的以太币。代币转账接口未判断转账者是否有足够的代币
-
转账前没有预估
gas
-
以太币转账,之前必须传入带小数位的金额。例如转账
1
ether,传入的参数为1000000000000000000
-
代币转账,之前必须先去查询该代币小数位,然后换算成带小数位的代币金额。例如转账
1
某token,传入的参数为1000000000000000000
-
代币转账,之前必须将转账方法名,转账人地址,金额,转换为十六进制,再拼接为
data
-
根据
password
和privateKey
返回keystore
。之前必须将privateKey
手动加上0x
前缀再进行传参,否则将返回错误的keystore
针对这些问题,对代码进行了修改:
-
修复了代币转账可能出现超时的问题
-
调用转账接口后,只要返回交易hash,则此交易必定为
success
状态,不需要再去查询交易状态。耗时取决于当前以太坊网络状况,经测试在15-60
秒左右 -
自动预估
gas
,移除了gasLimit
参数 -
以太币转账。现在自动判断转账方是否有足够的以太币
-
以太币转账。现在传入的以太币单位为
ether
,不需要
再加上18个0的小数位 -
代币转账。现在自动判断转账方是否有足够的代币
-
代币转账。
不需要
先去获取代币小数位,现在直接传入不带
小数位的代币金额。例如,之前某个代币小数位为8
,转账1
token,则传入100000000
。现在直接传入1
-
根据
password
和privateKey
返回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',
})
}
})
})
网友评论