'use strict'; const keys = require('./keys'); const blockchain = require('./blockchain'); const conversion_rates = require('./services/conversion').getRate; const bond_util = require('./services/bonds').util; const fund_util = require('./services/bobs-fund').util; const pCode = require('../docs/scripts/floExchangeAPI').processCode; const DB = require("./database"); const coupling = require('./coupling'); const price = require('./price'); const { PERIOD_INTERVAL, REQUEST_TIMEOUT } = require('./_constants')['background']; const { LAUNCH_SELLER_TAG, MAXIMUM_LAUNCH_SELL_CHIPS } = require('./_constants')["market"]; var assetList; //container and allowed assets var updateBalance; // container for updateBalance function const verifyTx = {}; function confirmDepositFLO() { DB.query("SELECT id, floID, txid FROM VaultTransactions WHERE mode=? AND asset=? AND asset_type=? AND r_status=?", [pCode.VAULT_MODE_DEPOSIT, "FLO", pCode.ASSET_TYPE_COIN, pCode.STATUS_PENDING]).then(results => { results.forEach(r => { verifyTx.FLO(r.floID, r.txid, keys.sink_groups.EXCHANGE).then(amount => { addSellChipsIfLaunchSeller(r.floID, amount).then(txQueries => { txQueries.push(updateBalance.add(r.floID, "FLO", amount)); txQueries.push(["UPDATE VaultTransactions SET r_status=?, amount=? WHERE id=?", [pCode.STATUS_SUCCESS, amount, r.id]]); DB.transaction(txQueries) .then(result => console.info("FLO deposited:", r.floID, amount)) .catch(error => console.error(error)) }).catch(error => console.error(error)) }).catch(error => { console.error(error); if (error[0]) DB.query("UPDATE VaultTransactions SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id]) .then(_ => null).catch(error => console.error(error)); }); }) }).catch(error => console.error(error)) } verifyTx.FLO = function (sender, txid, group) { return new Promise((resolve, reject) => { floBlockchainAPI.getTx(txid).then(tx => { let vin_sender = tx.vin.filter(v => v.addr === sender) if (!vin_sender.length) return reject([true, "Transaction not sent by the sender"]); if (vin_sender.length !== tx.vin.length) return reject([true, "Transaction input containes other floIDs"]); if (!tx.blockheight) return reject([false, "Transaction not included in any block yet"]); if (!tx.confirmations) return reject([false, "Transaction not confirmed yet"]); let amount = tx.vout.reduce((a, v) => keys.sink_chest.includes(group, v.scriptPubKey.addresses[0]) ? a + v.value : a, 0); if (amount == 0) return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes) else resolve(amount); }).catch(error => reject([false, error])) }) } function checkTag(floID, tag) { return new Promise((resolve, reject) => { DB.query("SELECT id FROM UserTag WHERE floID=? AND tag=?", [floID, tag]) .then(result => resolve(result.length ? true : false)) .catch(error => reject(error)) }) } function addSellChipsIfLaunchSeller(floID, quantity) { return new Promise((resolve, reject) => { checkTag(floID, LAUNCH_SELLER_TAG).then(result => { if (result) //floID is launch-seller Promise.all([ DB.query("SELECT IFNULL(SUM(quantity), 0) AS sold FROM TradeTransactions WHERE seller=? AND asset=?", [floID, 'FLO']), DB.query("SELECT IFNULL(SUM(quantity), 0) AS brought FROM TradeTransactions WHERE buyer=? AND asset=?", [floID, 'FLO']), DB.query("SELECT IFNULL(SUM(quantity), 0) AS chips FROM SellChips WHERE floID=? AND asset=?", [floID, 'FLO']), ]).then(result => { let sold = result[0][0].sold, brought = result[1][0].brought, chips = result[2][0].chips; let remLaunchChips = MAXIMUM_LAUNCH_SELL_CHIPS - (sold + chips) + brought; quantity = Math.min(quantity, remLaunchChips); if (quantity > 0) resolve([["INSERT INTO SellChips(floID, asset, quantity) VALUES (?)", [[floID, 'FLO', quantity]]]]); else resolve([]); }).catch(error => reject(error)) else //floID is not launch-seller resolve([]); }).catch(error => reject(error)) }) } function confirmDepositToken() { DB.query("SELECT id, floID, txid FROM VaultTransactions WHERE mode=? AND asset_type=? AND r_status=?", [pCode.VAULT_MODE_DEPOSIT, pCode.ASSET_TYPE_TOKEN, pCode.STATUS_PENDING]).then(results => { results.forEach(r => { verifyTx.token(r.floID, r.txid, keys.sink_groups.EXCHANGE).then(({ token, amount, flo_amount }) => { DB.query("SELECT id FROM VaultTransactions where floID=? AND mode=? AND asset=? AND asset_type=? AND txid=?", [r.floID, pCode.VAULT_MODE_DEPOSIT, "FLO", pCode.ASSET_TYPE_COIN, r.txid]).then(result => { let txQueries = []; //Add the FLO balance if necessary if (!result.length) { txQueries.push(updateBalance.add(r.floID, "FLO", flo_amount)); txQueries.push(["INSERT INTO VaultTransactions(txid, floID, mode, asset_type, asset, amount, r_status) VALUES (?)", [[r.txid, r.floID, pCode.VAULT_MODE_DEPOSIT, pCode.ASSET_TYPE_COIN, "FLO", flo_amount, pCode.STATUS_SUCCESS]]]); } txQueries.push(["UPDATE VaultTransactions SET r_status=?, asset=?, amount=? WHERE id=?", [pCode.STATUS_SUCCESS, token, amount, r.id]]); txQueries.push(updateBalance.add(r.floID, token, amount)); DB.transaction(txQueries) .then(result => console.info("Token deposited:", r.floID, token, amount)) .catch(error => console.error(error)); }).catch(error => console.error(error)); }).catch(error => { console.error(error); if (error[0]) DB.query("UPDATE VaultTransactions SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id]) .then(_ => null).catch(error => console.error(error)); }); }) }).catch(error => console.error(error)) } verifyTx.token = function (sender, txid, group, currencyOnly = false) { return new Promise((resolve, reject) => { floTokenAPI.getTx(txid).then(tx => { if (tx.parsedFloData.type !== "transfer") return reject([true, "Transaction type not 'transfer'"]); else if (tx.parsedFloData.transferType !== "token") return reject([true, "Transaction transfer is not 'token'"]); var token = tx.parsedFloData.tokenIdentification, amount = tx.parsedFloData.tokenAmount; if (currencyOnly && token !== floGlobals.currency) return reject([true, "Token not currency"]); else if (!currencyOnly && ((!assetList.includes(token) && token !== floGlobals.currency) || token === "FLO")) return reject([true, "Token not authorised"]); let vin_sender = tx.transactionDetails.vin.filter(v => v.addr === sender) if (!vin_sender.length) return reject([true, "Transaction not sent by the sender"]); let flo_amount = tx.transactionDetails.vout.reduce((a, v) => keys.sink_chest.includes(group, v.scriptPubKey.addresses[0]) ? a + v.value : a, 0); if (flo_amount == 0) return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes) else resolve({ token, amount, flo_amount }); }).catch(error => reject([false, error])) }) } function retryVaultWithdrawal() { DB.query("SELECT id, floID, asset, asset_type, amount FROM VaultTransactions WHERE mode=? AND r_status=?", [pCode.VAULT_MODE_WITHDRAW, pCode.STATUS_PENDING]).then(results => { results.forEach(r => { if (r.asset_type == pCode.ASSET_TYPE_COIN) { if (r.asset == "FLO") blockchain.withdrawAsset.retry(r.floID, r.asset, r.amount, r.id); } else if (r.asset_type == pCode.ASSET_TYPE_TOKEN) blockchain.withdrawAsset.retry(r.floID, r.asset, r.amount, r.id) }) }).catch(error => console.error(error)) } function confirmVaultWithdraw() { DB.query("SELECT id, floID, asset, asset_type, amount, txid FROM VaultTransactions WHERE mode=? AND r_status=?", [pCode.VAULT_MODE_WITHDRAW, pCode.STATUS_CONFIRMATION]).then(results => { results.forEach(r => { if (r.asset_type == pCode.ASSET_TYPE_COIN) { if (r.asset == "FLO") floBlockchainAPI.getTx(r.txid).then(tx => { if (!tx.blockheight || !tx.confirmations) //Still not confirmed return; DB.query("UPDATE VaultTransactions SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id]) .then(result => console.info("FLO withdrawed:", r.floID, r.amount)) .catch(error => console.error(error)) }).catch(error => console.error(error)); else if (r.asset == "BTC") btcOperator.getTx(r.txid).then(tx => { if (!tx.block || !tx.confirmations) //Still not confirmed return; DB.query("UPDATE VaultTransactions SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id]) .then(result => console.info("BTC withdrawed:", r.floID, r.amount)) .catch(error => console.error(error)) }).catch(error => console.error(error)); } else if (r.asset_type == pCode.ASSET_TYPE_TOKEN) floTokenAPI.getTx(r.txid).then(tx => { if (!tx.transactionDetails.blockheight || !tx.transactionDetails.confirmations) //Still not confirmed return; DB.query("UPDATE VaultTransactions SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id]) .then(result => console.info("Token withdrawed:", r.floID, r.asset, r.amount)) .catch(error => console.error(error)); }).catch(error => console.error(error)); }) }).catch(error => console.error(error)); } verifyTx.BTC = function (sender, txid, group) { return new Promise((resolve, reject) => { btcOperator.getTx(txid).then(tx => { let vin_sender = tx.inputs.filter(v => floCrypto.isSameAddr(v.address, sender)) if (!vin_sender.length) return reject([true, "Transaction not sent by the sender"]); if (vin_sender.length !== tx.inputs.length) return reject([true, "Transaction input containes other floIDs"]); if (!tx.block) return reject([false, "Transaction not included in any block yet"]); if (!tx.confirmations) return reject([false, "Transaction not confirmed yet"]); let amount = tx.outputs.reduce((a, v) => keys.sink_chest.includes(group, floCrypto.toFloID(v.address, { bech: [coinjs.bech32.version] })) ? a + parseFloat(v.value) : a, 0); if (amount == 0) return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes) else resolve(amount); }).catch(error => reject([false, error])) }) } function verifyConvert() { //Set all timeout convert request to refund mode (thus, asset will be refund if tx gets confirmed later) let req_timeout = new Date(Date.now() - REQUEST_TIMEOUT), to_refund_sql = "INSERT INTO RefundConvert (floID, in_txid, asset_type, asset, r_status)" + " SELECT floID, in_txid, ? AS asset_type, ? AS asset, r_status FROM DirectConvert" + " WHERE r_status=? AND locktime { DB.query("SELECT id, floID, mode, in_txid, amount, quantity FROM DirectConvert WHERE r_status=? AND coin=?", [pCode.STATUS_PENDING, "BTC"]).then(results => { results.forEach(r => { if (r.mode == pCode.CONVERT_MODE_GET) { verifyTx.token(r.floID, r.in_txid, keys.sink_groups.CONVERT, true).then(({ amount }) => { if (r.amount !== amount) throw ([true, "Transaction amount mismatched in blockchain"]); conversion_rates.BTC_INR().then(rate => { blockchain.convertToCoin.init(r.floID, "BTC", amount, rate, r.id) }).catch(error => console.error(error)) }).catch(error => { console.error(error); if (error[0]) DB.query("UPDATE DirectConvert SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id]) .then(_ => null).catch(error => console.error(error)); }); } else if (r.mode == pCode.CONVERT_MODE_PUT) { verifyTx.BTC(r.floID, r.in_txid, keys.sink_groups.CONVERT).then(quantity => { if (r.quantity !== quantity) throw ([true, "Transaction quantity mismatched in blockchain"]); conversion_rates.BTC_INR().then(rate => { blockchain.convertFromCoin.init(r.floID, quantity, rate, r.id) }).catch(error => console.error(error)) }).catch(error => { console.error(error); if (error[0]) DB.query("UPDATE DirectConvert SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id]) .then(_ => null).catch(error => console.error(error)); }); } }) }).catch(error => console.error(error)) }).catch(error => console.error(error)) } function retryConvert() { DB.query("SELECT id, floID, mode, amount, quantity FROM DirectConvert WHERE r_status=? AND coin=?", [pCode.STATUS_PROCESSING, "BTC"]).then(results => { results.forEach(r => { if (r.mode == pCode.CONVERT_MODE_GET) blockchain.convertToCoin.retry(r.floID, "BTC", r.quantity, r.id); else if (r.mode == pCode.CONVERT_MODE_PUT) blockchain.convertFromCoin.retry(r.floID, r.amount, r.id) }) }).catch(error => console.error(error)) } function confirmConvert() { DB.query("SELECT id, floID, mode, amount, quantity, out_txid FROM DirectConvert WHERE r_status=? AND coin=?", [pCode.STATUS_CONFIRMATION, "BTC"]).then(results => { results.forEach(r => { if (r.mode == pCode.CONVERT_MODE_GET) btcOperator.getTx(r.out_txid).then(tx => { if (!tx.block || !tx.confirmations) //Still not confirmed return; DB.query("UPDATE DirectConvert SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id]) .then(result => console.info(`${r.floID} converted ${r.amount} to ${r.quantity} BTC`)) .catch(error => console.error(error)) }).catch(error => console.error(error)); else if (r.mode == pCode.CONVERT_MODE_PUT) floTokenAPI.getTx(r.out_txid).then(tx => { if (!tx.transactionDetails.blockheight || !tx.transactionDetails.confirmations) //Still not confirmed return; DB.query("UPDATE DirectConvert SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id]) .then(result => console.info(`${r.floID} converted ${r.quantity} BTC to ${r.amount}`)) .catch(error => console.error(error)); }).catch(error => console.error(error)); }) }).catch(error => console.error(error)); } function verifyConvertFundDeposit() { DB.query("SELECT id, mode, txid, coin FROM ConvertFund WHERE r_status=? AND coin=?", [pCode.STATUS_PROCESSING, "BTC"]).then(results => { results.forEach(r => { if (r.mode == pCode.CONVERT_MODE_GET) { //deposit currency verifyTx.token(floGlobals.adminID, r.txid, keys.sink_groups.CONVERT, true).then(({ amount }) => { DB.query("UPDATE ConvertFund SET r_status=?, amount=? WHERE id=?", [pCode.STATUS_SUCCESS, amount, r.id]) .then(result => console.info(`Deposit-fund ${amount} ${floGlobals.currency} successful`)) .catch(error => console.error(error)); }).catch(error => { console.error(error); if (error[0]) DB.query("UPDATE ConvertFund SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id]) .then(_ => null).catch(error => console.error(error)); }); } else if (r.mode == pCode.CONVERT_MODE_PUT) {//deposit coin verifyTx.BTC(floGlobals.adminID, r.txid, keys.sink_groups.CONVERT).then(quantity => { DB.query("UPDATE ConvertFund SET r_status=?, quantity=? WHERE id=?", [pCode.STATUS_SUCCESS, quantity, r.id]) .then(result => console.info(`Deposit-fund ${quantity} ${r.coin} successful`)) .catch(error => console.error(error)); }).catch(error => { console.error(error); if (error[0]) DB.query("UPDATE ConvertFund SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id]) .then(_ => null).catch(error => console.error(error)); }); } }) }).catch(error => console.error(error)) } function retryConvertFundWithdraw() { DB.query("SELECT id, mode, coin, quantity, amount FROM ConvertFund WHERE r_status=? AND coin=?", [pCode.STATUS_PENDING, "BTC"]).then(results => { results.forEach(r => { if (r.mode == pCode.CONVERT_MODE_GET) //withdraw coin blockchain.convertFundWithdraw.retry(r.coin, r.quantity, r.id); else if (r.mode == pCode.CONVERT_MODE_PUT) //withdraw currency blockchain.convertFundWithdraw.retry(floGlobals.currency, r.amount, r.id); }) }).catch(error => console.error(error)) } function confirmConvertFundWithdraw() { DB.query("SELECT * FROM ConvertFund WHERE r_status=? AND coin=?", [pCode.STATUS_CONFIRMATION, "BTC"]).then(results => { results.forEach(r => { if (r.mode == pCode.CONVERT_MODE_GET) { //withdraw coin btcOperator.getTx(r.txid).then(tx => { if (!tx.block || !tx.confirmations) //Still not confirmed return; DB.query("UPDATE ConvertFund SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id]) .then(result => console.info(`Withdraw-fund ${r.quantity} ${r.coin} successful`)) .catch(error => console.error(error)) }).catch(error => console.error(error)); } else if (r.mode == pCode.CONVERT_MODE_PUT) {//withdraw currency floTokenAPI.getTx(r.txid).then(tx => { if (!tx.transactionDetails.blockheight || !tx.transactionDetails.confirmations) //Still not confirmed return; DB.query("UPDATE ConvertFund SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id]) .then(result => console.info(`Withdraw-fund ${r.amount} ${floGlobals.currency} successful`)) .catch(error => console.error(error)); }).catch(error => console.error(error)); } }) }).catch(error => console.error(error)) } function verifyConvertRefund() { DB.query("SELECT id, floID, asset_type, asset, in_txid FROM RefundConvert WHERE r_status=?", [pCode.STATUS_PENDING]).then(results => { results.forEach(r => { if (r.ASSET_TYPE_COIN) { if (r.asset == "BTC") //Convert is only for BTC right now verifyTx.BTC(r.floID, r.in_txid, keys.sink_groups.CONVERT) .then(amount => blockchain.refundConvert.init(r.floID, r.asset, amount, r.id)) .catch(error => { console.error(error); if (error[0]) DB.query("UPDATE RefundConvert SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id]) .then(_ => null).catch(error => console.error(error)); }); } else if (r.ASSET_TYPE_TOKEN) verifyTx.token(r.floID, r.in_txid, keys.sink_groups.CONVERT, true).then(({ amount }) => { blockchain.refundConvert.init(r.floID, floGlobals.currency, amount, r.id); }).catch(error => { console.error(error); if (error[0]) DB.query("UPDATE RefundConvert SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id]) .then(_ => null).catch(error => console.error(error)); }); }) }).catch(error => console.error(error)) } function retryConvertRefund() { DB.query("SELECT id, floID, asset, amount FROM RefundConvert WHERE r_status=?", [pCode.STATUS_PROCESSING]).then(results => { results.forEach(r => blockchain.refundConvert.retry(r.floID, r.asset, r.amount, r.id)) }).catch(error => console.error(error)) } function confirmConvertRefund() { DB.query("SELECT * FROM RefundConvert WHERE r_status=?", [pCode.STATUS_CONFIRMATION]).then(results => { results.forEach(r => { if (r.ASSET_TYPE_COIN) { if (r.asset == "BTC") //Convert is only for BTC right now btcOperator.getTx(r.out_txid).then(tx => { if (!tx.block || !tx.confirmations) //Still not confirmed return; DB.query("UPDATE RefundConvert SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id]) .then(result => console.info(`Refunded ${r.amount} ${r.asset} to ${r.floID}`)) .catch(error => console.error(error)) }).catch(error => console.error(error)); } else if (r.ASSET_TYPE_TOKEN) floTokenAPI.getTx(r.out_txid).then(tx => { if (!tx.transactionDetails.blockheight || !tx.transactionDetails.confirmations) //Still not confirmed return; DB.query("UPDATE RefundConvert SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id]) .then(result => console.info(`Refunded ${r.amount} ${r.asset} to ${r.floID}`)) .catch(error => console.error(error)); }).catch(error => console.error(error)); }) }).catch(error => console.error(error)) } function retryBondClosing() { DB.query("SELECT id, floID, amount, btc_net, usd_net FROM CloseBondTransact WHERE r_status=?", [pCode.STATUS_PENDING]).then(results => { results.forEach(r => blockchain.bondTransact.retry(r.floID, r.amount, r.btc_net, r.usd_net, r.id)) }).catch(error => console.error(error)) } function confirmBondClosing() { DB.query("SELECT * FROM CloseBondTransact WHERE r_status=?", [pCode.STATUS_CONFIRMATION]).then(results => { results.forEach(r => { btcOperator.getTx(r.txid).then(tx => { if (!tx.block || !tx.confirmations) //Still not confirmed return; let closeBondString = bond_util.stringify.end(r.bond_id, r.end_date, r.btc_net, r.usd_net, r.amount, r.ref_sign, r.txid); floBlockchainAPI.writeData(keys.node_id, closeBondString, keys.node_priv, bond_util.config.adminID).then(txid => { DB.query("UPDATE CloseBondTransact SET r_status=?, close_id=? WHERE id=?", [pCode.STATUS_SUCCESS, txid, r.id]) .then(result => console.info("Bond closed:", r.bond_id)) .catch(error => console.error(error)); }).catch(error => console.error(error)) }).catch(error => console.error(error)); }) }).catch(error => console.error(error)) } function retryFundClosing() { DB.query("SELECT id, floID, amount, btc_net, usd_net FROM CloseFundTransact WHERE r_status=?", [pCode.STATUS_PENDING]).then(results => { results.forEach(r => blockchain.fundTransact.retry(r.floID, r.amount, r.btc_net, r.usd_net, r.id)) }).catch(error => console.error(error)) } function confirmFundClosing() { DB.query("SELECT * FROM CloseFundTransact WHERE r_status=?", [pCode.STATUS_CONFIRMATION]).then(results => { results.forEach(r => { btcOperator.getTx(r.txid).then(tx => { if (!tx.block || !tx.confirmations) //Still not confirmed return; let closeFundString = fund_util.stringify.end(r.fund_id, r.floID, r.end_date, r.btc_net, r.usd_net, r.amount, r.ref_sign, r.txid); floBlockchainAPI.writeData(keys.node_id, closeFundString, keys.node_priv, fund_util.config.adminID).then(txid => { DB.query("UPDATE CloseFundTransact SET r_status=?, close_id=? WHERE id=?", [pCode.STATUS_SUCCESS, txid, r.id]) .then(result => console.info("Fund investment closed:", r.fund_id, r.floID)) .catch(error => console.error(error)); }).catch(error => console.error(error)) }).catch(error => console.error(error)); }) }).catch(error => console.error(error)) } //Periodic Process function processAll() { //deposit-withdraw asset balance if (keys.sink_chest.list(keys.sink_groups.EXCHANGE).length) { confirmDepositFLO(); confirmDepositToken(); retryVaultWithdrawal(); confirmVaultWithdraw(); } //convert service if (keys.sink_chest.list(keys.sink_groups.CONVERT).length) { verifyConvert(); retryConvert(); confirmConvert(); verifyConvertFundDeposit(); retryConvertFundWithdraw(); confirmConvertFundWithdraw(); verifyConvertRefund(); retryConvertRefund(); confirmConvertRefund(); } //blockchain-bond service if (keys.sink_chest.list(keys.sink_groups.BLOCKCHAIN_BONDS).length) { retryBondClosing(); confirmBondClosing(); } //bob's fund service if (keys.sink_chest.list(keys.sink_groups.EXCHANGE).length) { retryFundClosing(); confirmFundClosing(); } } var lastSyncBlockHeight = 0; function periodicProcess() { floBlockchainAPI.promisedAPI('api/blocks?limit=1').then(result => { if (lastSyncBlockHeight < result.blocks[0].height) { lastSyncBlockHeight = result.blocks[0].height; processAll(); console.log("Last Block :", lastSyncBlockHeight); } }).catch(error => console.error(error)); } function periodicProcess_start() { periodicProcess_stop(); periodicProcess(); assetList.forEach(asset => coupling.initiate(asset, true)); price.storeHistory.start(); periodicProcess.instance = setInterval(periodicProcess, PERIOD_INTERVAL); } function periodicProcess_stop() { if (periodicProcess.instance !== undefined) { clearInterval(periodicProcess.instance); delete periodicProcess.instance; } coupling.stopAll(); price.storeHistory.stop(); } module.exports = { blockchain, periodicProcess: { start: periodicProcess_start, stop: periodicProcess_stop }, set assetList(assets) { assetList = assets; }, set updateBalance(f) { updateBalance = f; } }