/* import moralis */ const Moralis = require("moralis/node"); var utils = require('./utils.js'); // var config = require('../config/config.js')(db_config, // moralis_config) var { moralis_config, reids_token_config, account_config } = require('../config/config.js') const redis = require("./db/redis_db") //导入 db.js const mysql = require("./db/mysql_db") const logger = require('./logger') const report = require("./report") //导入 db.js const BigNumber = require('bignumber.js') const collect_coins_db = require('./db/collect_coins_db') /* Moralis init code */ var serverUrl = moralis_config.SERVER_URL; var appId = moralis_config.APP_ID; var masterKey = moralis_config.MASTER_KEY; var moralisSecret = moralis_config.MORALIS_SECRET; // 内部异常 const ERROR_CODE_001 = -1; const SUCCEED_CODE = 0; // 归集队列 var collectCoinsArrays = []; // 是否执行归集 var isExecCollect = false; var masterInit = false var secretInit = false // 对 shib 做限制 const SHIB_AMOUNT_LIMIT = 20000 const SHIB_AMOUNT_FEE_LIMIT = 10000 const SHIB_ADDRESS = '0x2859e4544C4bB03966803b044A93563Bd2D0DD4D' // logger.log('Moralis.settings',Moralis) // Moralis.settings.setAPIRateLimit({ // anonymous:10, authenticated:20, windowMs:60000 // }) /** * 初始化 moralis * https://st94nif1cq.feishu.cn/docs/doccnNxG2UwHPCdZXbywgbdy13f# */ async function initMasterSDK() { logger.debug('initMasterSDK start') if (masterInit) { logger.log('initMasterSDK', masterInit) return } await Moralis.start({ serverUrl, appId, masterKey }); logger.debug('initMasterSDK start ok') // masterInit = true } async function initMoralisSecretSDK() { if (secretInit) { logger.log('initMoralisSecretSDK', secretInit) return } logger.debug('initMoralisSecretSDK start') await Moralis.start({ serverUrl, appId, moralisSecret }); logger.debug('initMoralisSecretSDK start ok') // secretInit = true } function toJson(code_, obj_, errMsg_) { return utils.toJson(code_, obj_, errMsg_); } /** * 通过交易记录获取 gas 费用 * @param {} type * @param {*} obj * @param {*} address */ function getTransferRecordGasFree(type, obj, address) { logger.log('getTransferRecordGasFree fun in', type, obj, address) if (obj && obj.data && obj.data.result && Array.isArray(obj.data.result) && obj.data.result.length > 0) { logger.log('getTransferRecordGasFree fun in', 1) for (let index = 0; index < obj.data.result.length; index++) { const element = obj.data.result[index]; logger.log('getTransferRecordGasFree fun in 2', element) if (element.type == type && element.to_address.toLowerCase() == address.toLowerCase()) { if (element.gas_price && element.gas && type == 'native') {//native return { totalGasFree: parseInt(element.gas_price) * parseInt(element.gas), gas_price: parseInt(element.gas_price), gas_limit: parseInt(element.gas), } } } } } return null; } function changeJsonHexBignumberToString(json) { if (json) { var obj = JSON.parse(json) var code = obj.code; logger.log('changeJsonHexBignumberToString obj=', obj) try { if (obj.data && obj.data.gasPrice && obj.data.gasLimit && obj.data.gasPrice.hex && obj.data.gasLimit.hex) { var curGasPrice = BigNumber(obj.data.gasPrice.hex).toNumber() var curGasLimit = BigNumber(obj.data.gasLimit.hex).toNumber() obj.data.gasPrice.number = curGasPrice.toString() obj.data.gasLimit.number = curGasLimit.toString() logger.log('getTransferGasFree totalGasFree=', curGasPrice, curGasLimit) if (obj.data.value && obj.data.value.hex) { var value = BigNumber(obj.data.value.hex).toNumber() logger.log('getTransferGasFree native value=', value) if (value != 0) { obj.data.value.number = value.toString() } } } logger.log('changeJsonHexBignumberToString ret=', obj.data) return JSON.stringify(obj.data) } catch (error) { logger.error('changeJsonHexBignumberToString error', error.toString()) return json; } } return json; } /** * 获取转账的 gas 费 * @param {*} type * @param {*} json * @returns */ function getTransferGasFree(type, json) { var curGasPrice = account_config.BNB_GAS_PRICE var curGasLimit = account_config.TOKEN_GAS_LIMIT var nativeValue = 0; var tokenValue = 0; var code = -1; if (type == 'native') { curGasLimit = account_config.BNB_GAS_LIMIT } var totalGasFree = parseInt(curGasLimit) * parseInt(curGasPrice); var gas_fee var gas_limit logger.log('getTransferGasFree json=', json) if (json) { var obj = JSON.parse(json) code = obj.code; logger.log('getTransferGasFree obj=', obj) if (obj.data && obj.data.gasPrice && obj.data.gasLimit && obj.data.gasPrice.hex && obj.data.gasLimit.hex) { var curGasPrice = BigNumber(obj.data.gasPrice.hex).toNumber() var curGasLimit = BigNumber(obj.data.gasLimit.hex).toNumber() if (curGasPrice > 0 && curGasLimit > 0) { totalGasFree = curGasPrice * curGasLimit; } gas_fee = curGasPrice gas_limit = gas_limit logger.log('getTransferGasFree totalGasFree=', curGasPrice, curGasLimit, totalGasFree) if (obj.data.value && obj.data.value.hex) { var value = BigNumber(obj.data.value.hex).toNumber() logger.log('getTransferGasFree native value=', value) if (value != 0) { nativeValue = value } } } } return { code: code, totalGasFree: totalGasFree, nativeValue: nativeValue, tokenValue: tokenValue, gasFee: gas_fee, gasLimit: gas_limit, } } /** * * @param {转账是否成功} json */ function isTransferSucceed(json) { if (json) { var obj = JSON.parse(json) return obj.code == 0 } return false } const withdraw = async (obj) => { obj.withdraw = 1; return await transfer_handle(obj); } async function getAccountBalances(options) { await initMasterSDK(); if (options.chain) { options.chain = utils.getChainName(options.chain) } //做 3次 重试,每次间隔 1s+= //Error: Request failed with status code 524 //Error: Request failed with status code 500 var tryCount = 4; var delay = 1000 var interval = 500 var result; var balance_opts = { address: options.address, chain: options.chain } logger.log('getAccountBalances :', options, balance_opts) do { try { if (options.type == 'native') { result = await Moralis.Web3API.account.getNativeBalance(balance_opts); logger.log('getNativeBalance=', result); } else { result = await Moralis.Web3API.account.getTokenBalances(balance_opts); logger.log('getTokenBalances=', result); } if (tryCount < 4) { logger.error('getBalances succeed:', JSON.stringify(balance_opts), " 已重试:" + tryCount + "次") } break } catch (error) { if (tryCount == 1) { logger.error('getBalances error:', error.toString(), JSON.stringify(balance_opts)) result = null; } tryCount -= 1; await utils.sleep(delay) delay += interval logger.error('getBalances error:', JSON.stringify(balance_opts), "重试:" + tryCount + "次") } } while (tryCount >= 1); return result } /** * 获取当前账户下所有不同种类的币 主流币 + 20币 * * 必填项 * obj.chain = ? * @param {*} obj */ async function getAccountAllCoins(obj) { var temp_chain = obj.chain; //拿到我当前所有的币种 obj.type = 'native' obj.chain = temp_chain var native_balance = await getAccountBalances(obj); //拿到我当前所有的 20 币 obj.type = '20' obj.chain = temp_chain var others_balances = await getAccountBalances(obj); return { native: native_balance, other: others_balances, } } /** * 判断是否转 gas 费 * @param {*} my_account_all_coins */ async function computeTransferGasFree(obj, my_account_all_coins, tokenPrices) { var totalCount = 0; var tokenCount = 0; var tokenGasPrice = 0; var nativeGasPrice = 0; var ret_total_gas_price = 0; var ret_total_count = 0; var ret_a_gas = 0; //需要转账的数组对象 var transfer_obj = []; var receiver_info = await queryCompanyInfoFromId(0); //得到 20 币 满足 1美刀的 count if (Array.isArray(my_account_all_coins.other) && my_account_all_coins.other.length > 0) { for (let index = 0; index < my_account_all_coins.other.length; index++) { const element = my_account_all_coins.other[index]; // my_account_all_coins.other.forEach(element => { logger.log('20 element=', element); var find_transfer_item = findTokenPriceItem(element.token_address, tokenPrices); if (element.token_address == SHIB_ADDRESS) { if (parseInt(element.balance) < SHIB_AMOUNT_LIMIT) { logger.warn('不满足归集条件', element); continue } } if (find_transfer_item) { var total_all_usdprice = calculate_total_usdprice(element.balance, element.decimals, find_transfer_item.usdPrice); logger.log('findTokenPriceItem ret=', element.token_address, find_transfer_item, total_all_usdprice); if (find_transfer_item && total_all_usdprice > 1.0) { tokenCount += 1; logger.log('token > 1.0', tokenCount, element.token_address); var obj_20 = { chain: obj.chain, contractAddress: element.token_address, amount: element.balance, receiver: receiver_info.user_address, type: 'erc20', address: obj.address, usdPrice: find_transfer_item.usdPrice } transfer_obj.push(obj_20) } } else { logger.log('findTokenPriceItem not fount =', element); } // }); } logger.log('account_config.TOKEN_GAS_LIMIT=', account_config.TOKEN_GAS_LIMIT); var lastTokenFree = await redis.readRedis(reids_token_config.LAST_TOTAL_TOKEN_FREE) logger.log('LAST_TOTAL_TOKEN_FREE=', lastTokenFree); if (lastTokenFree && reids_token_config.LAST_TOTAL_TOKEN_FREE && parseInt(lastTokenFree) > 0) { tokenGasPrice = parseInt(tokenCount) * parseInt(lastTokenFree); } else { tokenGasPrice = parseInt(tokenCount) * parseInt(account_config.TOKEN_GAS_LIMIT) * parseInt(account_config.BNB_GAS_PRICE); } ret_a_gas = account_config.BNB_GAS_PRICE; logger.log('tokenGasPrice=', tokenGasPrice); } //计算 native 是否满足 1美刀 logger.log('isTransferGasFree token count:', tokenCount); var nativeAllBalance = 0; if (my_account_all_coins.native && my_account_all_coins.native.balance) nativeAllBalance = my_account_all_coins.native.balance var nativeCount = 0 var nativePriceItem = findTokenPriceItem('0x0000000000000000000000000000000000000000', tokenPrices);//todo 线上环境需要换 logger.log('native nativePriceItem=', nativePriceItem, nativeAllBalance); if (nativePriceItem) { var total_all_usdprice = calculate_total_usdprice(nativeAllBalance, '18', nativePriceItem.usdPrice); logger.log('native total_all_usdprice=', total_all_usdprice); if (total_all_usdprice > 1.0) { nativeCount = 1; logger.log('native > 1.0', tokenCount); var lastBnbFree = await redis.readRedis(reids_token_config.LAST_TOTAL_BNB_FREE) logger.log('LAST_TOTAL_BNB_FREE=', lastBnbFree); if (lastBnbFree && parseInt(lastBnbFree) > 0) { nativeGasPrice = parseInt(nativeCount) * parseInt(lastBnbFree); } else { nativeGasPrice = parseInt(nativeCount) * parseInt(account_config.BNB_GAS_LIMIT) * parseInt(account_config.BNB_GAS_PRICE); } ret_a_gas = account_config.BNB_GAS_PRICE; // var real_native_amount = BigInt(nativeAllBalance) - BigInt(nativeGasPrice) - BigInt(tokenGasPrice); var real_native_amount = BigInt(nativeAllBalance); var obj_native = { chain: obj.chain, amount: real_native_amount.toString(), receiver: receiver_info.user_address, type: 'native', address: obj.address, usdPrice: nativePriceItem.usdPrice } transfer_obj.push(obj_native) } } logger.log('transfer obj=', transfer_obj) //计算所有币 totalCount = nativeCount + tokenCount; // var gasPrice = await redis.readRedis(reids_token_config.GASPRICE); logger.log('nativeAllBalance', nativeAllBalance); logger.log('totalCount', totalCount); //计算所有币转账所需要的 gas var total2Gas = nativeGasPrice + tokenGasPrice; var service_charge = 0; logger.log('total2Gas', total2Gas); //需要转账的 obj my_account_all_coins.transfer_arrays = transfer_obj //如果当前的钱不够 gas if (nativeAllBalance < total2Gas) { if (tokenCount > 0) {//出现 token 需要转移手续费 service_charge = 1; // total2Gas = (total2Gas - nativeAllBalance);//充手续费 logger.log('需要转账=', total2Gas); } } ret_total_gas_price = total2Gas.toString(); ret_total_count = totalCount; //返回结果 return { gasPrice: ret_total_gas_price, //需要 归集到用户地址的 gas 费转移 totalCount: ret_total_count, //一共归集次数 aGasPrice: ret_a_gas, //单个 gas 费用 get_service_charge: service_charge,//是否需要服务费 transfer_obj: transfer_obj, }; } function findTokenPriceItem(token_address, tokenPrices) { return tokenPrices.tokenPrice.find(element => { // logger.log('findTokenPriceItem find=', element.contract, token_address) return element.contract.toLowerCase() == token_address.toLowerCase(); }) } // function calculate_total_usdprice(amount, decimals, usdprice) { // return parseInt(amount) / (10**parseInt(decimals)) * parseInt(usdprice) ; function calculate_total_usdprice(amount, decimals, usdprice) { // if(usdprice.toString().indexOf("e") != -1 ){ // return parseInt(amount) / (10 ** parseInt(decimals)) * parseFloat(usdprice); // }else { // return parseInt(amount) / (10 ** parseInt(decimals)) * parseFloat(usdprice); // } return parseInt(amount) / (10 ** parseInt(decimals)) * parseFloat(usdprice); } function addNativeValue(nativeValue, aValue) { return BigInt(nativeValue) + BigInt(aValue) } function reduceNativeValue(nativeValue, rValue) { return BigInt(nativeValue) - BigInt(rValue) } /** * todo --> 计算 gas * @param {*} nativeBalance * @param {*} obj * @returns */ async function updateNativeBalance(nativeBalance, obj) { var temp = { ...obj } console.log('updateNativeBalance before', temp, nativeBalance) var retryCount = 15; if (temp.chain) { temp.chain = utils.getChainName(temp.chain) } console.log('updateNativeBalance after', temp, nativeBalance) do { //上面转账完 BNB 会减去,这里再获取一次 var native_ret = await Moralis.Web3API.account.getNativeBalance(temp); logger.log('更新余额 :', nativeBalance, native_ret, retryCount) if (nativeBalance != native_ret.balance && BigInt(nativeBalance) < BigInt(native_ret.balance)) { return native_ret.balance; } await utils.sleep(3000); retryCount--; } while (native_ret.balance == nativeBalance && retryCount > 0); return null; } //20 and native 归集 async function transfers(obj, my_account_all_coins, logParams) { // 优化后的归集 if (my_account_all_coins && my_account_all_coins.transfer_arrays && Array.isArray(my_account_all_coins.transfer_arrays) && my_account_all_coins.transfer_arrays.length > 0) { var t_i = 0; var totalGasFee = 0; var collects = [] var collects_error = [] var collects_mysql = [] for (let index = 0; index < my_account_all_coins.transfer_arrays.length; index++) { var ti = my_account_all_coins.transfer_arrays[index] logger.tlog('ti=', ti) //fix 归集失败的问题 //线上环境和测试环境由于访问快,导致余额未来得及更新,这里做一个 延迟判断 if (ti.type == 'native') { await updateNativeBalance(ti.amount, ti); } var ret = await start_collectCoins(ti) //更新 native 金额 if (isTransferSucceed(ret)) { logger.tlog('start_collectCoins respose...', ret); t_i += 1 collects.push(ret); //缓存当前交易的 gas 费用 if (ret && ti.contractAddress) { var tr = getTransferGasFree('token', ret) // tr.totalGasFree totalGasFee += parseInt(tr.totalGasFree); } else { var tr = getTransferGasFree('native', ret) // tr.totalGasFree totalGasFee += parseInt(tr.totalGasFree); } collects_mysql.push(changeJsonHexBignumberToString(ret)) } else { //缓存当前交易的 gas 费用 try { if (ret && ti.contractAddress) { var tr = getTransferGasFree('token', ret) // tr.totalGasFree totalGasFee += parseInt(tr.totalGasFree); } else { var tr = getTransferGasFree('native', ret) // tr.totalGasFree totalGasFee += parseInt(tr.totalGasFree); } try { collects_error.push(JSON.parse(ret).errMsg) } catch (error) { logger.error('collects_error json parse error', ret) } } catch (error) { logger.error('start_collectCoins 获取 gas fee=', error.toString(), JSON.stringify(ti)); } logger.tlog('start_collectCoins error=', ret); logger.error('start_collectCoins 归集 error=', ret, JSON.stringify(ti)); }; } //日志埋点-归集全部所花费的 gas 费用 logParams.collectCoinsTotalGasFee = totalGasFee.toString(); //日志埋点-归集的响应 logParams.results = collects logParams.collects = JSON.stringify(collects_mysql) if (obj.address) logParams.user_address = obj.address if (t_i == my_account_all_coins.transfer_arrays.length) { logger.log('归集日志上报:', logParams); logParams.type = report.REPORT_TYPE.collect_coins var info = await queryCompanyInfoFromId(0); logParams.receiver_address_total_balance_after = await queryCollectBalance(info.user_address, obj.chain) //归集入库 logParams.status = 0 try { await collect_coins_db.create_collect_coins_task({ ...logParams }) } catch (error) { logger.error('create_collect_coins_task', error.toString(), JSON.stringify(logParams)) } //日志埋点-日志上报 report.logReport(logParams) return toJson(0, null, '所有币归集成功.'); } else { //归集入库 logParams.status = -1 try { logger.error('归集失败 : ', JSON.stringify(collects_error)) logParams.errMsg = JSON.stringify(collects_error) await collect_coins_db.create_collect_coins_task({ ...logParams }) } catch (error) { logger.error('归集失败 error: ', error.toString()) } return toJson(-1, null, JSON.stringify(collects_error)); } } else { return toJson(-1, null, 'transfer conditions are not met.'); } } const start_collectCoins = async (obj) => { obj.withdraw = 0; return await transfer_handle(obj); } const transfer_handle = async (obj) => { //提币 if (obj.withdraw) { var id = 0 if (obj.privateKeyId) id = obj.privateKeyId //读取用户充币地址对应的私钥 var info = await queryCompanyInfoFromId(id); logger.log('transfer_handle queryCompanyInfoFromId=', info); //提币公司 obj.privateKey = info.user_private_key; // if (process.env.NODE_ENV != 'dev') { logger.log('readCompanyPriveteKeyFromMysql=', obj.privateKey) // } } else { //读取用户充币地址对应的私钥 obj.privateKey = await readPriveteKeyFromMysql(obj.address); } if (obj.privateKey && obj.privateKey.results) { obj.privateKey = obj.privateKey.results; } if (!obj.privateKey) { logger.error('readPriveteKeyFromMysql error.', obj); //提币公司 return toJson(-1, null, "readPriveteKeyFromMysql error."); } try { //解密 obj.privateKey = utils.decryptPrivityKey(obj.privateKey); if (!obj.privateKey) { logger.error('decryptPrivityKey error', obj); return toJson(-1, null, "decryptPrivityKey error."); } } catch (error) { logger.error('decryptPrivityKey error', error.toString(), JSON.stringify(obj)); if (!obj.privateKey) { return toJson(-1, null, "decryptPrivityKey error.", error.toString()); } } var ret = await transfer(obj); if (isTransferSucceed(ret)) { //缓存当前交易的 gas 费用 if (ret && obj.contractAddress) { var tr = getTransferGasFree('token', ret) logger.debug('cache setkey token LAST_TOTAL_TOKEN_FREE getTransferGasFree', tr) redis.redis_set(reids_token_config.LAST_TOTAL_TOKEN_FREE, tr.totalGasFree); } else { var tr = getTransferGasFree('native', ret) logger.debug('cache setkey LAST_TOTAL_BNB_FREE getTransferGasFree', tr) redis.redis_set(reids_token_config.LAST_TOTAL_BNB_FREE, tr.totalGasFree); } } else { var tm_obj = { ...obj } tm_obj.privateKey = '不对外暴露' logger.error('transfer_handle transfer error', JSON.stringify(obj)); } return ret; } async function queryCollectBalance(address, chain) { // var t_chain = utils.getChainName(chain) try { var queryCollectBalance = { address: address, chain: chain } logger.log('queryCollectBalance', queryCollectBalance) // 查询归集地址余额 // return await getAccountBalances(queryCollectBalance); return await getAccountAllCoins(queryCollectBalance); } catch (error) { logger.error('queryCollectBalance error', error.toString(), address, chain); return 0; } } /** * 用户充币地址的币转移到归集地址 * 1、检查当前账户的 主流币或者 20 币,是否满足一美刀,并且查看是否满足转账费用 n 如果不满足,先从归集地址 -> 用户提币地址(0.*2 来回2次转移) * 2、发起归集 用户账户 -> 从 mysql 拿到私钥进行解密 -> 转移到归集地址 * * * @param {*} obj */ const collectCoins = async (obj) => { logger.log('fun collectCoins in =', obj) var chain = obj.chain; //1、拿到当前账户所有的币 //2、是否满足交易费 如果不满足则 归集地址转移 币count * 手续费 到充币地址 //3、遍历所有币,开始转移到归集地址 // 日志上报的参数 var logParams = {}; var my_account_all_coins = await getAccountAllCoins(obj); logger.log('collectCoins getAccountAllCoins=', my_account_all_coins) if (!my_account_all_coins.native && !my_account_all_coins.other) { return 'getAccountAllCoins error.' + my_account_all_coins } //埋点日志-转账之前的充币地址余额 logParams.addressBalances = { ...my_account_all_coins }; //得到币价格 if (process.env.NODE_ENV == 'dev') { var test_json = '{"tokenPrice": [{"contract": "0x0000000000000000000000000000000000000000", "usdPrice": 400}, {"contract": "0x03716F32f72c692a0B355fa04639669E3341B94e", "usdPrice": 0.1}]}' obj.tokenPrices = JSON.parse(test_json); } else { obj.tokenPrices = await redis.readRedis(reids_token_config.TOKENPRICE) if (!obj.tokenPrices) { logger.error('readRedis TOKENPRICE error') return 'readRedis error' } if (typeof obj.tokenPrices == 'string') obj.tokenPrices = JSON.parse(obj.tokenPrices); } logger.log('tokenPrices=', obj.tokenPrices) obj.chain = chain; //计算 gas 费用 是否需要归集 var transFerGasFree = await computeTransferGasFree(obj, my_account_all_coins, obj.tokenPrices); //埋点日志- 需要归集的 native token if (transFerGasFree.transfer_obj && Array.isArray(transFerGasFree.transfer_obj)) { logParams.transfers = [] for (let index = 0; index < transFerGasFree.transfer_obj.length; index++) { const element = transFerGasFree.transfer_obj[index]; logParams.transfers.push({ ...element }) } } logger.log('computeTransferGasFree=', transFerGasFree) logger.log(' logParams.transfers=', logParams.transfers) //是否需要归集 if (transFerGasFree.totalCount > 0) { //需要转移 gas 费 //每次都需要充值 gas 费 if (account_config.TRANSFER_GAS || (parseInt(transFerGasFree.gasPrice) > 0 && transFerGasFree.get_service_charge == 1)) { var info = await queryCompanyInfoFromId(0); var obj_wd = { chain: chain, amount: transFerGasFree.gasPrice, receiver: obj.address, type: 'native', // address: account_config.WELLET_PUBLIC_KEY, //todo 正式环境需要替换从 mysql read address: info.user_address, //todo 正式环境需要替换从 mysql read } //埋点日志-预存 gas 付费 logParams.prestore_gas_fee = { ...obj_wd }; logParams.receiver_address_total_balance_before = await queryCollectBalance(info.user_address, chain) logParams.company_public_key = info.user_address logger.log('开始充值 gas ', obj_wd) var ret = await withdraw(obj_wd) logger.log('充值完成 gas ', ret) if (!isTransferSucceed(ret)) return ret; var transfer = getTransferGasFree('native', ret) logger.log('getTransferGasFree transfer =', transfer) if (transfer && transfer.nativeValue > 0) { logger.log('tempNativeValue=', my_account_all_coins.native.balance) var tempNativeValue = addNativeValue(my_account_all_coins.native.balance, transfer.nativeValue) my_account_all_coins.native.balance = tempNativeValue.toString(); logger.log('udpateNativeValue=', tempNativeValue); } else return "get native value error." } obj.chain = chain; obj.transFerGasFree = transFerGasFree; logger.log('transfers--->', obj); var ret = await transfers(obj, my_account_all_coins, logParams); logger.log('归集结果=', ret); return ret; } return obj.address + ':不满足归集条件'; } async function execCollectCoinsTask() { logger.log('execCollectCoinsTask in', collectCoinsArrays.length) if (isExecCollect) return isExecCollect = true; while (collectCoinsArrays.length > 0) { var obj = collectCoinsArrays.pop(); //开始收集用户地址里面的币到归集地址 var ret = await collectCoins(obj); // await utils.sleep(3000) logger.log('execCollectCoinsTask=', collectCoinsArrays.length, ret) } isExecCollect = false; logger.log('execCollectCoinsTask out', collectCoinsArrays.length) } function pushCollectConisObj(obj) { logger.log('collectCoinsArrays length=', collectCoinsArrays.length, collectCoinsArrays) if (collectCoinsArrays.length > 0) { var findItem = collectCoinsArrays.find(element => { return (obj.address == element.address) }) if (findItem) { logger.log('当前任务正在处理中...', obj.address) return; } } collectCoinsArrays.push(obj) execCollectCoinsTask(); } async function readPriveteKeyFromMysql(address) { return new Promise(resolve => { mysql.queryUserPrivateKeyFromUserAddress(address).then(ret => { logger.log('readPriveteKeyFromMysql=', ret); resolve(ret); }) }) } async function queryCompanyInfoFromId(id) { return new Promise(resolve => { mysql.queryCompanyInfoFromId(id).then(ret => { logger.log('queryCompanyInfoFromId=', ret, ret.results, ret.results.user_address, ret.results.user_private_key); resolve(ret.results); }) }) } const transfer = async (obj) => { logger.debug("fun transfer serverUrl ", serverUrl); logger.debug("fun transfer appId ", serverUrl); logger.debug("fun transfer moralisSecret ", moralisSecret); await initMoralisSecretSDK(); // initSDK(moralisSecret); logger.debug("fun transfer start ok "); const opts = {}; opts.chainId = 'bsc_testnet'; opts.privateKey = moralis_config.DEFAULT_PRIVATE_KEY; opts.type = "erc20"; //native erc20 if (!obj.receiver || !obj.amount || parseInt(obj.amount) <= 0) { logger.error("transfer fun transfer parameter error.", obj.receiver, obj.amount, obj.amount); return toJson(ERROR_CODE_001, null, "please check receiver or amount parameter is ok ?"); } if (obj.chain != null) { opts.chainId = utils.getChainId(obj.chain); console.log("chainId:", opts.chainId); } if (obj.type != null) { opts.type = obj.type; } if (obj.from_block != null) { opts.from_block = obj.from_block; } if (obj.to_block != null) { opts.to_block = obj.to_block; } opts.contractAddress = obj.contractAddress; opts.receiver = obj.receiver; //调用者传入 // opts.amount = Moralis.Units.Token(obj.amount, 18); opts.amount = obj.amount; if (obj.privateKey != null) { opts.privateKey = obj.privateKey; } var options; try { // sending 0.5 DAI tokens with 18 decimals on BSC testnet if (opts.contractAddress) { //如果存在就是代币 // if (opts.contractAddress == SHIB_ADDRESS) { // //提币金额必须大于 20000 ,手续费 10000 // if (parseInt(opts.amount) >= SHIB_AMOUNT_LIMIT) { // opts.amount = (parseInt(opts.amount) - SHIB_AMOUNT_FEE_LIMIT).toString() // } else { // return toJson(-1, null, "最低限额为 20000 个token."); // } // } options = Moralis.TransferOptions = { type: opts.type, amount: opts.amount, receiver: opts.receiver, //接收钱包地址 contractAddress: opts.contractAddress //用户合约地址 }; logger.tlog("options 20 =", options); } else { //ETH or BNB options = Moralis.TransferOptions = { type: opts.type, amount: opts.amount, receiver: opts.receiver, //接收钱包地址 }; logger.tlog("options native =", options, opts.chainId); } // Enable web3 await Moralis.enableWeb3({ //BSC mainnet = 0x38-56 testnet:0x61-97 chainId: opts.chainId, privateKey: opts.privateKey, }); logger.tlog("options id =", opts.chainId); var ret = await Moralis.transfer(options); logger.tlog("transfer 结果 =", ret); return toJson(SUCCEED_CODE, ret, ""); } catch (error) { logger.tlog('transfer error:', error); logger.error('重要消息-重要消息-重要消息 transfer error:', error.toString(), JSON.stringify(options)) if (error.reason != null) { return toJson(ERROR_CODE_001, null, error.toString()); } else { return toJson(ERROR_CODE_001, null, error.toString()); } } }; const getAllTokenWithdrawInfoLists = async (obj) => { try { var key = reids_token_config.TOKENWITHDRAW; var ret = await redis.readRedis(key); return toJson(SUCCEED_CODE, ret, null); } catch (error) { console.error("getAllTokenWithdrawInfoLists=", error); return toJson(ERROR_CODE_001, null, error.toString()); } } /** * 获取代币价格 -> usdPrice */ const getAllTotkenPrice = async () => { try { logger.log('当前环境:', process.env.NODE_ENV); logger.log("getAllTotkenPrice in", reids_token_config); // Prints "value" var token_price_key = reids_token_config.TOKENPRICE; logger.log("getAllTotkenPrice token_price_key=", token_price_key); return await redis.readRedis(token_price_key) } catch (error) { logger.error("getTotkenPrice=", error); return toJson(ERROR_CODE_001, null, error.toString()); } } function setTransfersDataType(type, ret) { if (ret && Array.isArray(ret) && ret.length > 0) { ret.forEach(element => { element.type = type; }); } } //获取交易记录 //hash 0xe09ba3a4c9f7a8902e01af68d0f1f91906f3f7db1195227e61c45c0e86b2630a async function getTokenTransfers(opt) { await initMasterSDK(); logger.log("fun getTokenTransfers in ", opt); const options = {}; options.type = 'all'; options.chain = 'bsc_mainnet'; if (opt.chain != null) { options.chain = utils.getChainName(opt.chain); logger.log('getTokenTransfers getChainName =', options.chain); } if (opt.order != null) { options.order = opt.order; } if (opt.startTime != null) { options.from_date = opt.startTime; } if (opt.endTime != null) { options.to_date = opt.endTime; } if (opt.from_block != null) { options.from_block = opt.from_block; } if (opt.to_block != null) { options.to_block = opt.to_block; } if (opt.transaction_hash) { options.transaction_hash = opt.transaction_hash; options.type = 'transaction_hash'; } logger.log('getTokenTransfers >>>>>', options); if (options.type == 'all') {//查询主流币和 20 币所有的交易 if (opt.address != null) { options.address = opt.address; } else { logger.error('getTokenTransfers error please check address parameter is ok ?', options); return toJson(ERROR_CODE_001, null, "please check address parameter is ok ?"); } var t_1 var t_2 // Error: Request failed with status code 524 // 做一次重试 var tryCount = 4; var delay = 1000 var interval = 500 do { //主流币 try { logger.log('getTokenTransfers account getTransactions>>>>>', options); t_1 = await Moralis.Web3API.account.getTransactions(options); logger.log('getTokenTransfers native ret -->>> t_1', t_1); setTransfersDataType('native', t_1.result) break } catch (error) { if (tryCount == 1) { logger.error("getTransactions error:", '主流币:', error.toString(), JSON.stringify(options)) return toJson(ERROR_CODE_001, null, error.toString()); } tryCount -= 1; await utils.sleep(delay) delay += interval } } while (tryCount >= 1); // Error: Request failed with status code 524 tryCount = 4 delay = 1000 interval = 500 do { //20币 try { //token 获取交易记录如果没有时间有些地址会失败 if (!options.to_block) { options.to_block = '10000000000' } logger.log('getTokenTransfers account getTokenTransfers>>>>>', options); t_2 = await Moralis.Web3API.account.getTokenTransfers(options); logger.log('getTokenTransfers token ret -->>> t_2', t_2); setTransfersDataType('token', t_2.result) break } catch (error) { if (tryCount == 1) { logger.error("getTransactions error:", 'token币:', error.toString(), JSON.stringify(options)) return toJson(ERROR_CODE_001, null, error.toString()); } tryCount -= 1; await utils.sleep(delay) delay += interval } } while (tryCount >= 1); //异常 if (t_2 && t_2.total > 0 && Array.isArray(t_2.result) && t_2.result.length <= 0) { logger.error('getTokenTransfers token 数据异常 -->>>', t_2.toString(), JSON.stringify(options)); return toJson(ERROR_CODE_001, null, 'token 数据异常.'); } //排序组合 try { let arr = t_1.result; let arr1 = t_2.result; if (Array.isArray(arr1) && Array.isArray(arr)) { let arr2 = arr.concat(arr1); t_1.result = arr2; logger.log('getTokenTransfers-->>> concat t_1', t_1); if (t_1.total) { t_1.total = t_1.result.length } } //将结果排序 t_1.result.sort((a, b) => { let t1 = new Date(Date.parse(a.block_timestamp)) let t2 = new Date(Date.parse(b.block_timestamp)) return t2.getTime() - t1.getTime() }) logger.log('getTokenTransfers-->>> sort t_1', t_1); return toJson(SUCCEED_CODE, t_1, null); } catch (error) { logger.error("getTransactions 排序组合 error :", error.toString(), JSON.stringify(options), JSON.stringify(t_1), JSON.stringify(t_2)) return toJson(ERROR_CODE_001, null, error.toString()); } } else if (options.type == 'transaction_hash') {//根据哈希查询 var tryCount = 2 do { try { logger.log('transaction_hash getTransaction options-->>> ', options); //native const transaction = await Moralis.Web3API.native.getTransaction(options); var arr = []; if (transaction) arr.push(transaction) var obj = { result: arr } logger.log('transaction_hash getTransaction ret-->>> ', transaction, obj); return toJson(SUCCEED_CODE, obj, null); } catch (error) { if (tryCount == 1) { logger.error("native getTransaction error:", error.toString(), JSON.stringify(options)) return toJson(ERROR_CODE_001, null, error.toString()); } tryCount -= 1; } } while (tryCount >= 1); } else { return toJson(ERROR_CODE_001, null, "This type is not supported.");; } } module.exports = { transfer, getTokenTransfers, toJson, getAllTokenWithdrawInfoLists, getAllTotkenPrice, withdraw, collectCoins, isTransferSucceed, getTransferRecordGasFree, pushCollectConisObj, getTransferGasFree, queryCollectBalance, queryCompanyInfoFromId, }