/* 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("./redis_db") //导入 db.js const mysql = require("./mysql_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; /** * 初始化 moralis * https://st94nif1cq.feishu.cn/docs/doccnNxG2UwHPCdZXbywgbdy13f# */ async function initMasterSDK() { await Moralis.start({ serverUrl, appId, masterKey }); } async function initMoralisSecretSDK() { await Moralis.start({ serverUrl, appId, moralisSecret }); } function toJson(code_, obj_, errMsg_) { return utils.toJson(code_, obj_, errMsg_); } const withdraw = async (obj) => { obj.withdraw = 1; return await transfer(obj); } async function getAccountBalances(options) { await initMasterSDK(); if (options.chain) { options.chain = utils.getChainName(options.chain) } console.log('getAccountBalances :', options) if (options.type == 'native') { var opt_ret = await Moralis.Web3API.account.getNativeBalance(options); console.log('getNativeBalance=', opt_ret); return opt_ret } else { var aar = await Moralis.Web3API.account.getTokenBalances(options); console.log('getTokenBalances=', aar); return aar; } } /** * 获取当前账户下所有不同种类的币 主流币 + 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(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; //得到 20 币 满足 1美刀的 count if (Array.isArray(my_account_all_coins.other) && my_account_all_coins.other.length > 0) { my_account_all_coins.other.forEach(element => { console.debug('20 element=', element); var find_transfer_item = findTokenPriceItem(element.token_address, tokenPrices); var total_all_usdprice = calculate_total_usdprice(element.balance, element.decimals, find_transfer_item.usdPrice); console.debug('findTokenPriceItem ret=', element.token_address, find_transfer_item, total_all_usdprice); if (find_transfer_item && total_all_usdprice > 1.0) { tokenCount += 1; console.debug('token > 1.0', tokenCount, element.token_address); } }); console.log('account_config.TOKEN_GAS_LIMIT=', account_config.TOKEN_GAS_LIMIT); tokenGasPrice = parseInt(tokenCount) * parseInt(account_config.TOKEN_GAS_LIMIT) * parseInt(account_config.BNB_GAS_PRICE); ret_a_gas = account_config.BNB_GAS_PRICE; console.log('tokenGasPrice=', tokenGasPrice); } //计算 native 是否满足 1美刀 console.log('isTransferGasFree token count:', tokenCount); var nativeAllBalance = my_account_all_coins.native.balance; var nativeCount = 0 var nativePriceItem = findTokenPriceItem('0x0000000000000000000000000000000000000000', tokenPrices);//todo 线上环境需要换 console.debug('native nativePriceItem=', nativePriceItem, nativeAllBalance); if (nativePriceItem) { var total_all_usdprice = calculate_total_usdprice(nativeAllBalance, '18', nativePriceItem.usdPrice); console.debug('native total_all_usdprice=', total_all_usdprice); if (total_all_usdprice > 1.0) { nativeCount = 1; console.debug('native > 1.0', tokenCount); nativeGasPrice = parseInt(nativeCount) * parseInt(account_config.BNB_GAS_LIMIT) * parseInt(account_config.BNB_GAS_PRICE); ret_a_gas = account_config.BNB_GAS_PRICE; } } //计算所有币转账所需要的 gas totalCount = nativeCount + tokenCount; // var gasPrice = await redis.readRedis(reids_token_config.GASPRICE); console.log('nativeAllBalance', nativeAllBalance); console.log('totalCount', totalCount); var total2Gas = nativeGasPrice + tokenGasPrice; var service_charge = 0; console.log('total2Gas', total2Gas); //如果当前的钱不够 gas if (nativeAllBalance < total2Gas) { if (tokenCount > 0) {//出现 token 需要转移手续费 service_charge = 1; total2Gas = (total2Gas - nativeAllBalance);//充手续费 console.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,//是否需要服务费 }; } function findTokenPriceItem(token_address, tokenPrices) { return tokenPrices.tokenPrice.find(element => { console.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) { return parseInt(amount) / (10 ** parseInt(decimals)) * parseFloat(usdprice); } async function updateNativeBalance(nativeBalance, obj) { var temp = obj var retryCount = 30; do { //上面转账完 BNB 会减去,这里再获取一次 var native_ret = await Moralis.Web3API.account.getNativeBalance(temp); console.log('更新余额 :', native_ret, retryCount) if (nativeBalance != native_ret.balance) { return native_ret.balance; } await utils.sleep(3000); retryCount--; } while (native_ret.balance == nativeBalance && retryCount > 0); return null; } //支持多账户转账 async function transfers(obj, my_account_all_coins) { var address = obj.address; var chain = obj.chain; var tokenPrices = obj.tokenPrices; console.log('tokenPrices=', tokenPrices, my_account_all_coins); if (!my_account_all_coins || !tokenPrices) return 'error.' console.log(' my_account_all_coins.other.lenth=', my_account_all_coins.other.length); var isUpdateNativeBalance = 0; //token 归集 if (my_account_all_coins.other && Array.isArray(my_account_all_coins.other) && my_account_all_coins.other.length > 0) { var available = Array.isArray(tokenPrices.tokenPrice) && tokenPrices.tokenPrice.length > 0 if (!available) return -1; for (let i = 0; i < my_account_all_coins.other.length; ++i) { var transfer_item = my_account_all_coins.other[i]; if (my_account_all_coins.other[i].token_address != null) { var find_transfer_item = findTokenPriceItem(transfer_item.token_address, tokenPrices); //todo 计算 token 币价格 * token美元单价 if (find_transfer_item && calculate_total_usdprice(transfer_item.balance, transfer_item.decimals, find_transfer_item.usdPrice) > 1.0) { var obj_20 = { chain: obj.chain, contractAddress: transfer_item.token_address, amount: transfer_item.balance, receiver: account_config.WELLET_PUBLIC_KEY, type: 'erc20', address: address, } console.log('start_collectCoins erc20:', obj_20); console.log('calculate_total_usdprice 20', calculate_total_usdprice(transfer_item.balance, transfer_item.decimals, '0.1')); isUpdateNativeBalance = 1; var ret = await start_collectCoins(obj_20) console.log('start_collectCoins erc20 respose...', ret); } } else { console.error('token Must be greater than a dollar.', transfer_item.balance, transfer_item.decimals); return toJson(-1, null, 'token Must be greater than a dollar.'); } } } //native 归集 if (my_account_all_coins.native) { console.log('查询本地余额参数=', obj) if (obj.chain) { obj.chain = utils.getChainName(obj.chain) } console.log('查询本地余额 before', my_account_all_coins.native) if (isUpdateNativeBalance) { // do { var updateBalance = await updateNativeBalance(my_account_all_coins.native.balance, obj) if (updateBalance) my_account_all_coins.native.balance = updateBalance; else return 'updateNativeBalance error. ' // //上面转账完 BNB 会减去,这里再获取一次 // var native_ret = await Moralis.Web3API.account.getNativeBalance(obj); // console.log('更新余额 :', native_ret) // if (my_account_all_coins.native.balance != native_ret.balance) { // my_account_all_coins.native.balance = native_ret.balance; // break // } // await utils.sleep(1000); // } while (native_ret.balance == my_account_all_coins.native.balance); } obj.chain = chain; console.log('查询本地余额 after', my_account_all_coins.native) var find_native_item = findTokenPriceItem('0x0000000000000000000000000000000000000000', tokenPrices);//todo 线上环境需要换 var nativeCoins = calculate_total_usdprice(my_account_all_coins.native.balance, '18', find_native_item.usdPrice); console.log('start_collectCoins nativeCoins:', nativeCoins, obj); console.log('start_collectCoins obj:', obj); console.log('start_collectCoins native.balance:', my_account_all_coins.native.balance); //todo 计算 token 币价格 * token美元单价 if (find_native_item && nativeCoins > 1.0) { console.log('native.balance', my_account_all_coins.native.balance) console.log('aGasPrice', obj.transFerGasFree.aGasPrice) console.log('gasLimint', account_config.BNB_GAS_LIMIT) // var gasPrice = BigInt(obj.transFerGasFree.aGasPrice); var gasPrice = BigInt(account_config.BNB_GAS_PRICE); var gasLimit = BigInt(account_config.BNB_GAS_LIMIT); var nativeBalance = BigInt(my_account_all_coins.native.balance); console.log('native.balance>>>', nativeBalance) console.log('aGasPrice>>>', gasPrice) console.log('gasLimint>>>', gasLimit) var real_native_amount = nativeBalance - gasPrice * gasLimit; console.log('start_collectCoins native amount:', real_native_amount.toString()); obj = { chain: chain, amount: real_native_amount.toString(), receiver: account_config.WELLET_PUBLIC_KEY, type: 'native', address: address, } // obj = { // chain: 'bsc_testnet', // amount: real_native_amount.toString(), // receiver: account_config.WELLET_PUBLIC_KEY, // type: 'native', // address: '0x7C7401fcc82D1e53C4090561c3e6fde80d74e317', // } console.log('start_collectCoins native:', obj); console.log('calculate_total_usdprice native', nativeCoins, find_native_item); return await start_collectCoins(obj) } else { return toJson(-1, null, 'native Must be greater than a dollar.'); } } } const start_collectCoins = async (obj) => { obj.withdraw = 0; return await transfer(obj); } /** * 用户充币地址的币转移到归集地址 * 1、检查当前账户的 主流币或者 20 币,是否满足一美刀,并且查看是否满足转账费用 n 如果不满足,先从归集地址 -> 用户提币地址(0.*2 来回2次转移) * 2、发起归集 用户账户 -> 从 mysql 拿到私钥进行解密 -> 转移到归集地址 * * * @param {*} obj */ const collectCoins = async (obj) => { var chain = obj.chain; //1、拿到当前账户所有的币 //2、是否满足交易费 如果不满足则 归集地址转移 币count * 手续费 到充币地址 //3、遍历所有币,开始转移到归集地址 var my_account_all_coins = await getAccountAllCoins(obj); //得到币价格 obj.tokenPrices = await redis.readRedis(reids_token_config.TOKENPRICE) if (typeof obj.tokenPrices == 'string') obj.tokenPrices = JSON.parse(obj.tokenPrices); //计算 gas 费用 是否需要归集 var transFerGasFree = await computeTransferGasFree(my_account_all_coins, obj.tokenPrices); console.log('computeTransferGasFree=', transFerGasFree) //是否需要归集 if (transFerGasFree.totalCount > 0) { //需要转移 gas 费 if (parseInt(transFerGasFree.gasPrice) > 0 && transFerGasFree.get_service_charge == 1) { var obj_wd = { chain: chain, amount: transFerGasFree.gasPrice, receiver: obj.address, type: 'native', address: account_config.WELLET_PUBLIC_KEY, //todo 正式环境需要替换从 mysql read } console.log('开始充值 gas ', obj_wd) var ret = await withdraw(obj_wd) console.log('充值完成 gas ', ret) //更新本地余额 var updateBalance = await updateNativeBalance(my_account_all_coins.native.balance, obj) if (updateBalance) my_account_all_coins.native.balance = updateBalance; else return "error." } obj.chain = chain; obj.transFerGasFree = transFerGasFree; console.log('transfers--->', obj); var ret = await transfers(obj, my_account_all_coins); console.log('归集结果=', ret); return ret; } return obj.address+':不满足归集条件'; } var collectCoinsArrays = []; async function execCollectCoinsTask() { // new Promise((resolve) => { while (collectCoinsArrays.length > 0) { var obj = collectCoinsArrays.pop(); //开始收集用户地址里面的币到归集地址 var ret = await collectCoins(obj); await utils.sleep(10000); console.log('execCollectCoinsTask=', obj, collectCoinsArrays.length) } // resolve('result'); // }) } function pushCollectConisObj(obj) { collectCoinsArrays.push(obj) execCollectCoinsTask(); } async function readPriveteKeyFromMysql(address) { return new Promise(resolve => { mysql.queryUserPrivateKeyFromUserAddress(address).then(ret => { console.log('readPriveteKeyFromMysql=', ret); resolve(ret); }) }) } const transfer = async (obj) => { console.debug("fun transfer in ", obj); console.debug("fun transfer serverUrl ", serverUrl); console.debug("fun transfer appId ", serverUrl); console.debug("fun transfer moralisSecret ", moralisSecret); await initMoralisSecretSDK(); // initSDK(moralisSecret); console.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) { console.error("fun transfer parameter error."); 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 privateKey = ''; //提币 if (obj.withdraw) { //提币公司 opts.privateKey = account_config.WELLET_PRIVATE_KEY; } else { //读取用户充币地址对应的私钥 opts.privateKey = await readPriveteKeyFromMysql(obj.address); if (opts.privateKey && opts.privateKey.results) { opts.privateKey = opts.privateKey.results; } } if (!opts.privateKey) { return toJson(-1, null, "decryptPrivityKey error."); } opts.privateKey = utils.decryptPrivityKey(opts.privateKey); console.log('decryptPrivityKey privateKey=', opts.privateKey); try { // sending 0.5 DAI tokens with 18 decimals on BSC testnet var options; if (opts.contractAddress) { //如果存在就是代币 options = Moralis.TransferOptions = { type: opts.type, amount: opts.amount, receiver: opts.receiver, //接收钱包地址 contractAddress: opts.contractAddress //用户合约地址 }; console.log("options 20 =", options); } else { //ETH or BNB options = Moralis.TransferOptions = { type: opts.type, amount: opts.amount, receiver: opts.receiver, //接收钱包地址 }; console.log("options native =", options, opts.chainId); } // Enable web3 await Moralis.enableWeb3({ //BSC mainnet = 0x38-56 testnet:0x61-97 chainId: opts.chainId, privateKey: opts.privateKey, }); var ret = await Moralis.transfer(options); return toJson(SUCCEED_CODE, ret, ""); } catch (error) { console.log('transfer error:', error); if (error.reason != null) { return toJson(ERROR_CODE_001, null, error.toString()); } else { return toJson(ERROR_CODE_001, null, error);; } } }; 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 { console.log('当前环境:', process.env.NODE_ENV); console.log("getAllTotkenPrice in", reids_token_config); // Prints "value" var token_price_key = reids_token_config.TOKENPRICE; console.log("getAllTotkenPrice token_price_key=", token_price_key); return await redis.readRedis(token_price_key) } catch (error) { console.error("getTotkenPrice=", error); return toJson(ERROR_CODE_001, null, error.toString()); } } //获取交易记录 //hash 0xe09ba3a4c9f7a8902e01af68d0f1f91906f3f7db1195227e61c45c0e86b2630a async function getTokenTransfers(opt) { await initMasterSDK(); console.debug("fun getTokenTransfers in ", opt); const options = {}; options.type = 'all'; options.chain = 'bsc_mainnet'; if (opt.chain != null) { options.chain = utils.getChainName(opt.chain); console.log('getTokenTransfers=', 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'; } console.debug('getTokenTransfers-->>>', options); if (options.type == 'all') {//查询主流币和 20 币所有的交易 try { if (opt.address != null) { options.address = opt.address; } else { return toJson(ERROR_CODE_001, null, "please check address parameter is ok ?"); } //主流币 var t_1 = await Moralis.Web3API.account.getTransactions(options); //20币 var t_2 = await Moralis.Web3API.account.getTokenTransfers(options); 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; } return toJson(SUCCEED_CODE, t_1, null); } catch (error) { console.error("getTransactions error:", error) return toJson(ERROR_CODE_001, null, error);; } } else if (options.type == 'transaction_hash') {//根据哈希查询 try { //native const transaction = await Moralis.Web3API.native.getTransaction(options); var arr = []; if (transaction) arr.push(transaction) var obj = { result: arr } return toJson(SUCCEED_CODE, obj, null); } catch (error) { console.error("native getTransaction error:", error) return toJson(ERROR_CODE_001, null, error);; } } else { return toJson(ERROR_CODE_001, null, "This type is not supported.");; } } module.exports = { transfer, getTokenTransfers, toJson, getAllTokenWithdrawInfoLists, getAllTotkenPrice, withdraw, collectCoins, pushCollectConisObj, }