From b2e51a9502b5feed779447fb4158d9c97d8d3c58 Mon Sep 17 00:00:00 2001 From: chaosBreaking Date: Wed, 4 Sep 2019 22:58:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=8C=BA=E5=9D=97?= =?UTF-8?q?=E9=93=BE=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/common/rootWallet.js | 19 +++++++ src/utils/abi.js | 1 + src/utils/erc20.js | 108 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 src/common/rootWallet.js create mode 100644 src/utils/abi.js create mode 100644 src/utils/erc20.js diff --git a/package.json b/package.json index 6b3ac66..191b92d 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "cron": "^1.7.1", "deepmerge": "^4.0.0", "errorhandler": "^1.5.0", + "ethers": "^4.0.36", "express": "^4.17.1", "js-sha3": "^0.8.0", "jsonwebtoken": "^8.5.1", diff --git a/src/common/rootWallet.js b/src/common/rootWallet.js new file mode 100644 index 0000000..9f47a61 --- /dev/null +++ b/src/common/rootWallet.js @@ -0,0 +1,19 @@ +'use strict'; +// 该脚本只用于系统账户对用户进行提现转账 +const { ERC20 } = require('../utils/erc20'); +const { inject } = require('./Provider'); + +const USDTAddr = '0xdac17f958d2ee523a2206206994597c13d831ec7'; +const RootAccount = inject(function (SysConfig) { + const { ROOTSECWORD } = SysConfig; + return ERC20.fromMnemonic(ROOTSECWORD, USDTAddr); +}); + +module.exports = { + async sendUSDT2User (toAddress, amount, option = {}) { + return await RootAccount.transfer(toAddress, amount, option); + }, + async sendVIC2User (toAddress, amount, option = {}) { + return await RootAccount.transfer(toAddress, amount, Object.assign(option, { decimals: 18 })); + } +}; diff --git a/src/utils/abi.js b/src/utils/abi.js new file mode 100644 index 0000000..1a3e450 --- /dev/null +++ b/src/utils/abi.js @@ -0,0 +1 @@ +module.exports = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_upgradedAddress","type":"address"}],"name":"deprecate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"deprecated","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_evilUser","type":"address"}],"name":"addBlackList","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"upgradedAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balances","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"maximumFee","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"_totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_maker","type":"address"}],"name":"getBlackListStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowed","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newBasisPoints","type":"uint256"},{"name":"newMaxFee","type":"uint256"}],"name":"setParams","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"issue","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"redeem","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"basisPointsRate","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isBlackListed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_clearedUser","type":"address"}],"name":"removeBlackList","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MAX_UINT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_blackListedUser","type":"address"}],"name":"destroyBlackFunds","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_initialSupply","type":"uint256"},{"name":"_name","type":"string"},{"name":"_symbol","type":"string"},{"name":"_decimals","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Issue","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Redeem","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newAddress","type":"address"}],"name":"Deprecate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint256"},{"indexed":false,"name":"maxFee","type":"uint256"}],"name":"Params","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_blackListedUser","type":"address"},{"indexed":false,"name":"_balance","type":"uint256"}],"name":"DestroyedBlackFunds","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_user","type":"address"}],"name":"AddedBlackList","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_user","type":"address"}],"name":"RemovedBlackList","type":"event"},{"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"},{"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"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"}] \ No newline at end of file diff --git a/src/utils/erc20.js b/src/utils/erc20.js new file mode 100644 index 0000000..a2d0653 --- /dev/null +++ b/src/utils/erc20.js @@ -0,0 +1,108 @@ +'use strict'; +// const eth = require('etherscan-api').init('E3ZFFAEMNN33KX4HHVUZ4KF8XY1FXMR4BI'); +// const eth = require('etherscan-api').init('IHXJ7P2W4J9DQRBIR5IB84JA5DMZ5HB3D9'); + +const axios = require('axios'); +const crypto = require('./crypto'); +const ethers = require('ethers'); + +require('setimmediate'); + +const ETH_NODE = 'https://mainnet.infura.io/v3/fa90db22229c4ee08fbec357579adb23'; +const GAS_UNIT_GWEI = 1e9; // 1 gwei = 1e9 wei +const GAS_Fee_ERC20 = 0.000060; +const GAS_LIMIT_ERC20 = 60000; + + +class ERC20 extends ethers.Wallet { + constructor(privateKey, contractAddress = '') { + if (!privateKey) throw new Error('PrivateKey required'); + super(privateKey); + this.contractAddress = contractAddress; + } + static fromMnemonic(secword, contractAddress, path = '') { + let seckey = crypto.secword2keypair(secword, { coin: 'ETH', path }).seckey; + return new ERC20(seckey, contractAddress); + } + static async getDecimals(contractAddress) { + if (!contractAddress) throw new Error('Missing params'); + let queryAddress = '0x313ce567' + (contractAddress.split('x')[1]).padStart(64, '0'); + let params = [{ "to": contractAddress, "data": queryAddress }, "latest"]; + let queryData = { + "jsonrpc": "2.0", + "method": "eth_call", + "params": params, + "id": 6842 + }; + return parseInt((await axios.post(ETH_NODE, queryData)).data.result); + } + static async getBalance(address, contractAddress) { + if (!address || !contractAddress) throw new Error('Missing params'); + let queryAddress = '0x70a08231' + (address.split('x')[1]).padStart(64, '0'); + let params = [{ "to": contractAddress, "data": queryAddress }, "latest"]; + let queryData = { + "jsonrpc": "2.0", + "method": "eth_call", + "params": params, + "id": 6842 + }; + // return parseInt(erc20res.result)/Number('10'.padEnd(ERC20Table[obj.name].decimals+1,'0')) + let res = (await axios.post(ETH_NODE, queryData)).data.result; + if (res == '0x') return 0; + return parseInt(res); + } + async getBalance() { + return ERC20.getBalance(this.address, this.contractAddress); + } + async getDecimals() { + let decimals = await ERC20.getDecimals(this.contractAddress); + if (decimals) + Object.defineProperty(this, 'decimals', { + enumerable: true, + value: decimals, + writable: false + }); + else + return 0; // any good idea? + } + async getTransactionCount() { + if (!ETH_NODE) { throw new Error('Base url required'); } + return (await axios.post(ETH_NODE, { + "jsonrpc": "2.0", "method": "eth_getTransactionCount", "params": [this.signingKey.address, "latest"], "id": 1 + })).data.result || null; + } + async transfer(toAddress, amount, option = {}) { + const { gasPrice = GAS_Fee_ERC20, gasLimit = GAS_LIMIT_ERC20, decimals = 6 } = option; + let nonce = await this.getTransactionCount(); + if (!nonce) nonce = '0x1'; + let txBody = '0x' + 'a9059cbb' + toAddress.split('x')[1].padStart(64, '0') + Number(amount * Math.pow(10, decimals)).toString(16).padStart(64, '0'); + let transaction = { + nonce: nonce, + gasLimit: gasLimit, + gasPrice: ethers.utils.bigNumberify(gasPrice * GAS_UNIT_GWEI), // Gwei + to: this.contractAddress, + value: 0, + data: txBody + }; + let signedTransaction = await this.sign(transaction); + try { + let erc20TxRes = (await axios.post(ETH_NODE, { + "jsonrpc": "2.0", + "method": "eth_sendRawTransaction", + "params": [signedTransaction.toString('hex')], + "id": 6842 + })).data; + if (!erc20TxRes || erc20TxRes.error) + throw new Error(erc20TxRes.error); + return erc20TxRes.result; + } + catch (err) { + throw new Error(err); + } + } +} + +module.exports = { + ERC20 +}; +