From 6fca38ba0177d14b9fc67bf791385e3c95884576 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Thu, 22 Feb 2018 13:24:32 -0500 Subject: [PATCH 01/22] Update remoteShare.js --- lib/remoteShare.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/remoteShare.js b/lib/remoteShare.js index 0c564aef..317e4ab4 100644 --- a/lib/remoteShare.js +++ b/lib/remoteShare.js @@ -31,7 +31,7 @@ function messageHandler(message) { process.on('message', messageHandler); -app.post('/leafApi', function (req, res) { +app.post('/api/leaf', function (req, res) { try { let msgData = global.protos.WSData.decode(req.body); if (msgData.key !== global.config.api.authKey) { From b20c0f5b17941db0d7807cec588631fdf23a4728 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Thu, 22 Feb 2018 13:40:26 -0500 Subject: [PATCH 02/22] Update remoteShare.js --- lib/remoteShare.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/remoteShare.js b/lib/remoteShare.js index 317e4ab4..689cb6d7 100644 --- a/lib/remoteShare.js +++ b/lib/remoteShare.js @@ -31,7 +31,7 @@ function messageHandler(message) { process.on('message', messageHandler); -app.post('/api/leaf', function (req, res) { +app.post('/leaf', function (req, res) { try { let msgData = global.protos.WSData.decode(req.body); if (msgData.key !== global.config.api.authKey) { From 0fc47ea110304b186332b53cd38e9b1abf8d8292 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Thu, 1 Mar 2018 23:00:36 -0500 Subject: [PATCH 03/22] Create etn.js --- lib/payment_systems/etn.js | 696 +++++++++++++++++++++++++++++++++++++ 1 file changed, 696 insertions(+) create mode 100644 lib/payment_systems/etn.js diff --git a/lib/payment_systems/etn.js b/lib/payment_systems/etn.js new file mode 100644 index 00000000..e3f19f5d --- /dev/null +++ b/lib/payment_systems/etn.js @@ -0,0 +1,696 @@ +"use strict"; +const shapeshift = require('shapeshift.io'); +const async = require("async"); +const debug = require("debug")("payments"); +const request = require('request-json'); +const range = require('range'); + +let hexChars = new RegExp("[0-9a-f]+"); +let bestExchange = global.config.payout.bestExchange; +let xmrAPIClient = request.createClient('https://xmr.to/api/v1/xmr2btc/'); +let extraPaymentRound = false; +let paymentTimer = null; + +let shapeshiftQueue = async.queue(function (task, callback) { + // Amount needs to be shifted in as a non-completed value, as the wallet will only take non-complete values.. + let amount = task.amount - task.fee; + // Address is the destination address IN BTC. + let address = task.address; + // PaymentIDs are the paymentID's to flag as paid by this transaction. + // Should be a massive list of ID's so we can bulk-update them, by merging them with 's. + // Here we go! General process: Scan shapeshift for valid amounts of funds to xfer around. + // Once there's enough funds, then we active txn + // Do a wallet call to xfer. + // Setup a monitor on the transaction + async.waterfall([ + function (intCallback) { + // Verify if the coin is active in ShapeShift first. + shapeshift.coins(function (err, coinData) { + if (err) { + intCallback(err); + } else if (!coinData.hasOwnProperty(global.config.general.coinCode) || coinData[global.config.general.coinCode].status !== "available") { + intCallback("Coin " + global.config.general.coinCode + " Is not available at this time on shapeshift."); + } else { + intCallback(null); + } + }); + }, + function (intCallback) { + // Get the market information from shapeshift, which includes deposit limits, minimum deposits, rates, etc. + shapeshift.marketInfo(global.config.payout.shapeshiftPair, function (err, marketInfo) { + if (err) { + intCallback(err); + } else if (!marketInfo.hasOwnProperty("limit") || marketInfo.limit <= global.support.coinToDecimal(amount)) { + intCallback("Not enough coin in shapeshift to process at this time."); + } else if (!marketInfo.hasOwnProperty("min") || marketInfo.min >= global.support.coinToDecimal(amount)) { + intCallback("Not enough coin to hit the shapeshift minimum deposits."); + } else { + intCallback(null, marketInfo); + } + }); + }, + function (marketInfo, intCallback) { + // Validated there's enough coin. Time to make our dank txn. + // Return: + /* + { + "orderId": "cc49c556-e645-4c15-a943-d50a935274e4", + "sAddress": "46yzCCD3Mza9tRj7aqPSaxVbbePtuAeKzf8Ky2eRtcXGcEgCg1iTBio6N4sPmznfgGEUGDoBz5CLxZ2XPTyZu1yoCAG7zt6", + "deposit": "d8041668718e6e9d9d0fd335ee5ecd923e6fd074c41316d041cc18b779ade10e", + "depositType": "XMR", + "withdrawal": "1DbxcoCBSA9N7uZvkcvWxuLxSau9q9Pwiu", + "withdrawalType": "BTC", + "public": null, + "apiPubKey": "shapeshift", + "returnAddress": "46XWBqE1iwsVxSDP1qDrxhE1XvsZV6eALG5LwnoMdjbT4GPdy2bZTb99kagzxp2MMjUamTYZ4WgvZdFadvMimTjvR6Gv8hL", + "returnAddressType": "XMR" + } + Valid Statuses: + "received" + "complete" + "error" + "no_deposits" + Complete State Information: + { + "status": "complete", + "address": "d8041668718e6e9d9d0fd335ee5ecd923e6fd074c41316d041cc18b779ade10e", + "withdraw": "1DbxcoCBSA9N7uZvkcvWxuLxSau9q9Pwiu", + "incomingCoin": 3, + "incomingType": "XMR", + "outgoingCoin": "0.04186155", + "outgoingType": "BTC", + "transaction": "be9d97f6fc75262151f8f63e035c6ed638b9eb2a4e93fef43ea63124b045dbfb" + } + */ + shapeshift.shift(address, global.config.payout.shapeshiftPair, {returnAddress: global.config.pool.address}, function (err, returnData) { + if (err) { + intCallback(err); + } else { + global.mysql.query("INSERT INTO shapeshiftTxn (id, address, paymentID, depositType, withdrawl, withdrawlType, returnAddress, returnAddressType, txnStatus) VALUES (?,?,?,?,?,?,?,?,?)", + [returnData.orderId, returnData.sAddress, returnData.deposit, returnData.depositType, returnData.withdrawl, returnData.withdrawlType, returnData.returnAddress, returnData.returnAddressType, 'no_deposits']).then(function () { + intCallback(null, marketInfo, returnData); + }).catch(function (error) { + intCallback(error); + }); + } + }); + }, + function (marketInfo, shapeshiftTxnData, intCallback) { + // Make the payment to ShapeShift + let paymentDetails = { + destinations: [ + { + amount: amount, + address: shapeshiftTxnData.sAddress + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn, + payment_id: shapeshiftTxnData.deposit + }; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 0.0000001) { + intCallback(null, marketInfo, shapeshiftTxnData, body); + } else { + intCallback("Unknown error from the wallet."); + } + }); + }, + function (marketInfo, shapeshiftTxnData, body, intCallback) { + // body.tx_hash = XMR transaction hash. + // Need to add transaction. + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees, exchange_rate, exchange_name, exchange_txn_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + [1, address, null, task.amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, global.support.decimalToCoin(marketInfo.minerFee), 1, global.support.decimalToCoin(marketInfo.rate), 'shapeshift', shapeshiftTxnData.orderId]).then(function (result) { + intCallback(null, result.insertId); + }).catch(function (error) { + intCallback(error); + }); + } + ], function (err, result) { + if (err) { + console.error("Error processing shapeshift txn: " + JSON.stringify(err)); + callback(true); + } else { + // Need to fill out this data pronto! + console.log("Processed ShapeShift transaction for: " + address + " Paid out: " + result + " payments in the db"); + callback(null, result); + } + }); +}, 2); + +let xmrToQueue = async.queue(function (task, callback) { + // http://xmrto-api.readthedocs.io/en/latest/introduction.html + // Documentation looks good! + // Amount needs to be shifted in as a non-completed value, as the wallet will only take non-complete values.. + let amount = task.amount - task.fee; + // Address is the destination address IN BTC. + let address = task.address; + // PaymentIDs are the paymentID's to flag as paid by this transaction. + // Should be a massive list of ID's so we can bulk-update them, by merging them with 's. + // Here we go! General process: Scan shapeshift for valid amounts of funds to xfer around. + // Once there's enough funds, then we active txn + // Do a wallet call to xfer. + // Setup a monitor on the transaction + async.waterfall([ + function (intCallback) { + // Verify if XMR.to is ready to get to work. + xmrAPIClient.get('order_parameter_query/', function (err, res, body) { + if (err) { + return intCallback(err); + } else if (body.error_msg) { + return intCallback(body.error_msg); + } else { + let amtOfBTC = ((amount / global.config.general.sigDivisor) * body.price).toPrecision(5); + console.log("Attempting to pay: " + address + " Amount: " + amtOfBTC + " BTC or " + amount / global.config.general.sigDivisor + " XMR"); + console.log("Response from XMR.to: " + JSON.stringify(body)); + if (body.lower_limit >= amtOfBTC) { + return intCallback("Not enough XMR to hit the minimum deposit"); + } else if (body.upper_limit <= amtOfBTC) { + return intCallback("Too much XMR to pay out to xmr.to"); + } else { + return intCallback(null, amtOfBTC); + } + } + }); + }, + function (btcValue, intCallback) { + // Validated there's enough coin. Time to make our dank txn. + // Return: + /* + { + "state": "TO_BE_CREATED", + "btc_amount": , + "btc_dest_address": "", + "uuid": "" + } + Valid Statuses: + "TO_BE_CREATED" + "UNPAID" + "UNDERPAID" + "PAID_UNCONFIRMED" + "PAID" + "BTC_SENT" + "TIMED_OUT" + "NOT_FOUND" + // Create, then immediately update with the new information w/ a status call. + */ + console.log("Amount of BTC to pay: " + btcValue); + xmrAPIClient.post('order_create/', { + btc_amount: btcValue, + btc_dest_address: address + }, function (err, res, body) { + if (err) { + return intCallback(err); + } else if (body.error_msg) { + return intCallback(body.error_msg); + } else { + return intCallback(null, body.uuid); + } + }); + }, + function (txnID, intCallback) { + // This function only exists because xmr.to is a pretty little fucking princess. + async.doUntil(function (xmrCallback) { + xmrAPIClient.post('order_status_query/', {uuid: txnID}, function (err, res, body) { + if (err) { + return intCallback(err); + } else if (body.error_msg) { + return intCallback(body.error_msg); + } else { + xmrCallback(null, body.state); + } + }); + }, + function (xmrCallback) { + return xmrCallback !== "TO_BE_CREATED"; + }, + function () { + intCallback(null, txnID); + }); + }, + function (txnID, intCallback) { + xmrAPIClient.post('order_status_query/', {uuid: txnID}, function (err, res, body) { + if (err) { + return intCallback(err); + } else if (body.error_msg) { + return intCallback(body.error_msg); + } else { + console.log(JSON.stringify(body)); + global.mysql.query("INSERT INTO xmrtoTxn (id, address, paymentID, depositType, withdrawl, withdrawlType, returnAddress, returnAddressType, txnStatus, amountDeposited, amountSent) VALUES (?,?,?,?,?,?,?,?,?,?,?)", + [txnID, body.xmr_receiving_address, body.xmr_required_payment_id, 'XMR', body.btc_dest_address, 'BTC', global.config.pool.address, 'XMR', body.state_str, global.support.decimalToCoin(body.xmr_amount_total), global.support.decimalToCoin(body.btc_amount)]).then(function () { + return intCallback(null, body, global.support.decimalToCoin(body.xmr_amount_total)); + }).catch(function (error) { + return intCallback(error); + }); + } + }); + }, + function (orderStatus, xmrDeposit, intCallback) { + // Make the payment to ShapeShift + let paymentDetails = { + destinations: [ + { + amount: xmrDeposit, + address: orderStatus.xmr_receiving_address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn, + payment_id: orderStatus.xmr_required_payment_id + }; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 0.0000001) { + return intCallback(null, orderStatus, body); + } else { + return intCallback("Unknown error from the wallet."); + } + }); + }, + function (orderStatus, body, intCallback) { + // body.tx_hash = XMR transaction hash. + // Need to add transaction. + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees, exchange_rate, exchange_name, exchange_txn_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + [1, address, null, global.support.decimalToCoin(orderStatus.xmr_amount_total), body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1, global.support.decimalToCoin(orderStatus.xmr_price_btc), 'xmrto', orderStatus.uuid]).then(function (result) { + return intCallback(null, result.insertId); + }).catch(function (error) { + return intCallback(error); + }); + } + ], function (err, result) { + if (err) { + console.error("Error processing XMRTo txn: " + JSON.stringify(err)); + return callback("Error!"); + } else { + // Need to fill out this data pronto! + console.log("Processed XMRTo transaction for: " + address + " Paid out: " + result + " payments in the db"); + return callback(null, result); + } + }); +}, 2); + +let paymentQueue = async.queue(function (paymentDetails, callback) { + /* + support JSON URI: http://10.0.0.2:28082/json_rpc Args: {"id":"0","jsonrpc":"2.0","method":"transfer","params":{"destinations":[{"amount":68130252045355,"address":"A2MSrn49ziBPJBh8ZNEhhbfyLMou6mao4C1F5TLGUatmUnCxZArDYkcbAnVkVEopWVeak2rKDrmc8JpoS7n5dvfN9YDPBTG"}],"mixin":4,"payment_id":"7e52c5266de9fede7fb3abc0cd88f937b38b51426f7b34ff99729d28ce4e1142"}} +1ms + payments Payment made: {"id":"0","jsonrpc":"2.0","result":{"fee":40199391255,"tx_hash":"c418708643f72635edf522490bfb2cae9d42a6dc1df30dcde844862dfd88f5b3","tx_key":""}} +2s + */ + if (paymentTimer !== null){ + clearInterval(paymentTimer); + paymentTimer = null; + } + debug("Making payment based on: " + JSON.stringify(paymentDetails)); + let transferFunc = 'transfer'; + global.support.rpcWallet(transferFunc, paymentDetails, function (body) { + debug("Payment made: " + JSON.stringify(body)); + if (body.hasOwnProperty('error')) { + if (body.error.message === "not enough money"){ + console.error("Issue making payments, not enough money, will try later"); + if(!extraPaymentRound){ + setTimeout(function(){ + makePayments(); + }, global.config.payout.timerRetry * 60 * 1000); + } + extraPaymentRound = true; + return callback(false); + } else { + console.error("Issue making payments" + JSON.stringify(body.error)); + console.error("Will not make more payments until the payment daemon is restarted!"); + //toAddress, subject, body + global.support.sendEmail(global.config.general.adminEmail, "Payment daemon unable to make payment", + "Hello,\r\nThe payment daemon has hit an issue making a payment: " + JSON.stringify(body.error) + + ". Please investigate and restart the payment daemon as appropriate"); + return; + } + } + if (paymentDetails.hasOwnProperty('payment_id')) { + console.log("Payment made to " + paymentDetails.destinations[0].address + " with PaymentID: " + paymentDetails.payment_id + " For: " + global.support.coinToDecimal(paymentDetails.destinations[0].amount) + " XMR with a " + global.support.coinToDecimal(body.result.fee) + " XMR Mining Fee"); + return callback(body.result); + } else { + if (transferFunc === 'transfer') { + console.log("Payment made out to multiple people, total fee: " + global.support.coinToDecimal(body.result.fee) + " XMR"); + } + let intCount = 0; + paymentDetails.destinations.forEach(function (details) { + console.log("Payment made to: " + details.address + " For: " + global.support.coinToDecimal(details.amount) + " XMR"); + intCount += 1; + if (intCount === paymentDetails.destinations.length) { + return callback(body.result); + } + }); + } + }); +}, 1); + +paymentQueue.drain = function(){ + extraPaymentRound = false; + if (global.config.payout.timer > 35791){ + console.error("Payout timer is too high. Please use a value under 35791 to avoid overflows."); + } else { + paymentTimer = setInterval(makePayments, global.config.payout.timer * 60 * 1000); + } + global.database.setCache('lastPaymentCycle', Math.floor(Date.now()/1000)); +}; + +function updateShapeshiftCompletion() { + global.mysql.query("SELECT * FROM shapeshiftTxn WHERE txnStatus NOT IN ('complete', 'error')").then(function (rows) { + rows.forEach(function (row) { + shapeshift.status(row.paymentID, function (err, status, returnData) { + if (err) { + return; + } + global.mysql.query("UPDATE shapeshiftTxn SET txnStatus = ? WHERE id = ?", [status, row.id]).then(function () { + if (status === 'complete') { + global.mysql.query("UPDATE shapeshiftTxn SET amountDeposited = ?, amountSent = ?, transactionHash = ? WHERE id = ?", + [global.support.decimalToCoin(returnData.incomingCoin), global.support.bitcoinDecimalToCoin(returnData.outgoingCoin), returnData.transaction, row.id]).then(function () { + global.mysql.query("UPDATE transactions SET confirmed = 1, confirmed_time = now(), btc_amt = ? WHERE exchange_txn_id = ?", [global.support.bitcoinDecimalToCoin(returnData.outgoingCoin), row.id]); + }); + } else if (status === 'error') { + // Failed txn. Need to rollback and delete all related data. Here we go! + global.mysql.query("DELETE FROM shapeshiftTxn WHERE id = ?", [row.id]); + global.mysql.query("SELECT id, xmr_amt, address FROM transactions WHERE exchange_txn_id = ?", [row.id]).then(function (rows) { + global.mysql.query("DELETE FROM transactions WHERE id = ?", [rows[0].id]); + global.mysql.query("DELETE payments WHERE transaction_id = ?", [rows[0].id]); + global.mysql.query("UPDATE balance SET amount = amount+? WHERE payment_address = ? limit 1", [rows[0].xmr_amt, rows[0].address]); + }); + console.error("Failed transaction from ShapeShift " + JSON.stringify(returnData)); + } + }); + }); + }); + }); +} + +function updateXMRToCompletion() { + global.mysql.query("SELECT * FROM xmrtoTxn WHERE txnStatus NOT IN ('PAID', 'TIMED_OUT', 'NOT_FOUND', 'BTC_SENT')").then(function (rows) { + rows.forEach(function (row) { + xmrAPIClient.post('order_status_query/', {uuid: row.id}, function (err, res, body) { + if (err) { + console.log("Error in getting order status: " + JSON.stringify(err)); + return; + } + if (body.error_msg) { + console.log("Error in getting order status: " + body.error_msg); + return; + } + global.mysql.query("UPDATE xmrtoTxn SET txnStatus = ? WHERE id = ?", [body.state, row.id]).then(function () { + if (body.status === 'BTC_SENT') { + global.mysql.query("UPDATE xmrtoTxn SET transactionHash = ? WHERE id = ?", [body.btc_transaction_id, row.id]).then(function () { + global.mysql.query("UPDATE transactions SET confirmed = 1, confirmed_time = now(), btc_amt = ? WHERE exchange_txn_id = ?", [global.support.bitcoinDecimalToCoin(body.btc_amount), row.id]); + }); + } else if (body.status === 'TIMED_OUT' || body.status === 'NOT_FOUND') { + global.mysql.query("DELETE FROM xmrtoTxn WHERE id = ?", [row.id]); + global.mysql.query("SELECT id, xmr_amt, address FROM transactions WHERE exchange_txn_id = ?", [row.id]).then(function (rows) { + global.mysql.query("DELETE FROM transactions WHERE id = ?", [rows[0].id]); + global.mysql.query("DELETE payments WHERE transaction_id = ?", [rows[0].id]); + global.mysql.query("UPDATE balance SET amount = amount+? WHERE payment_address = ? limit 1", [rows[0].xmr_amt, rows[0].address]); + }); + console.error("Failed transaction from XMRto " + JSON.stringify(body)); + } + }); + }); + }); + }); +} + +function determineBestExchange() { + async.waterfall([ + function (callback) { + // Verify if the coin is active in ShapeShift first. + shapeshift.coins(function (err, coinData) { + if (err) { + return callback(err); + } else if (!coinData.hasOwnProperty(global.config.general.coinCode) || coinData[global.config.general.coinCode].status !== "available") { + return callback("Coin " + global.config.general.coinCode + " Is not available at this time on shapeshift."); + } else { + return callback(null); + } + }); + }, + function (callback) { + // Get the market information from shapeshift, which includes deposit limits, minimum deposits, rates, etc. + shapeshift.marketInfo(global.config.payout.shapeshiftPair, function (err, marketInfo) { + if (err) { + return callback(err); + } else if (!marketInfo.hasOwnProperty("rate")) { + return callback("Shapeshift did not return the rate."); + } else { + return callback(null, global.support.bitcoinDecimalToCoin(marketInfo.rate)); + } + }); + }, + function (ssValue, callback) { + xmrAPIClient.get('order_parameter_query/', function (err, res, body) { + console.log("XMR.to pricing body: " + JSON.stringify(body)); + if (err) { + return callback(err); + } else if (body.error_msg) { + return callback(body.error_msg); + } else { + return callback(null, ssValue, global.support.bitcoinDecimalToCoin(body.price)); + } + }); + } + ], function (err, ssValue, xmrToValue) { + if (err) { + return console.error("Error processing exchange value: " + JSON.stringify(err)); + } + debug("ShapeShift Value: " + global.support.bitcoinCoinToDecimal(ssValue) + " XMR.to Value: " + global.support.bitcoinCoinToDecimal(xmrToValue)); + if (ssValue >= xmrToValue) { + console.log("ShapeShift is the better BTC exchange, current rate: " + global.support.bitcoinCoinToDecimal(ssValue)); + bestExchange = 'shapeshift'; + global.mysql.query("UPDATE config SET item_value = 'shapeshift' where item='bestExchange'"); + global.mysql.query("UPDATE config SET item_value = ? where item='exchangeRate'", [ssValue]); + } else { + console.log("XMR.to is the better BTC exchange, current rate: " + global.support.bitcoinCoinToDecimal(xmrToValue)); + bestExchange = 'xmrto'; + global.mysql.query("UPDATE config SET item_value = 'xmrto' where item='bestExchange'"); + global.mysql.query("UPDATE config SET item_value = ? where item='exchangeRate'", [xmrToValue]); + } + }); +} + +function Payee(amount, address, paymentID, bitcoin) { + this.amount = amount; + this.address = address; + this.paymentID = paymentID; + this.bitcoin = bitcoin; + this.blockID = 0; + this.poolType = ''; + this.transactionID = 0; + this.sqlID = 0; + if (paymentID === null) { + this.id = address; + } else { + this.id = address + "." + paymentID; + } + this.fee = 0; + this.baseFee = global.support.decimalToCoin(global.config.payout.feeSlewAmount); + this.setFeeAmount = function () { + if (this.amount <= global.support.decimalToCoin(global.config.payout.walletMin)) { + this.fee = this.baseFee; + } else if (this.amount <= global.support.decimalToCoin(global.config.payout.feeSlewEnd)) { + let feeValue = this.baseFee / (global.support.decimalToCoin(global.config.payout.feeSlewEnd) - global.support.decimalToCoin(global.config.payout.walletMin)); + this.fee = this.baseFee - ((this.amount - global.support.decimalToCoin(global.config.payout.walletMin)) * feeValue); + } + this.fee = Math.floor(this.fee); + }; + + this.makePaymentWithID = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn, + payment_id: this.paymentID + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let paymentID = this.paymentID; + let payee = this; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 0.0000001) { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, address, paymentID, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.makePaymentAsIntegrated = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let payee = this; + + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 0.0000001) { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?)", + [0, address, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.makeBitcoinPayment = function () { + let functionalData = {address: this.address, amount: this.amount, fee: this.fee}; + let payee = this; + if (bestExchange === 'xmrto') { + xmrToQueue.push(functionalData, function (err, transactionID) { + if (err) { + return console.error("Error processing payment for " + functionalData.address); + } + payee.transactionID = transactionID; + payee.trackPayment(); + }); + } else { + shapeshiftQueue.push(functionalData, function (err, transactionID) { + if (err) { + return console.error("Error processing payment for " + functionalData.address); + } + payee.transactionID = transactionID; + payee.trackPayment(); + }); + } + }; + + this.trackPayment = function () { + global.mysql.query("UPDATE balance SET amount = amount - ? WHERE id = ?", [this.amount, this.sqlID]); + global.mysql.query("INSERT INTO payments (unlocked_time, paid_time, pool_type, payment_address, transaction_id, bitcoin, amount, payment_id, transfer_fee)" + + " VALUES (now(), now(), ?, ?, ?, ?, ?, ?, ?)", [this.poolType, this.address, this.transactionID, this.bitcoin, this.amount - this.fee, this.paymentID, this.fee]); + }; +} + +function makePayments() { + global.mysql.query("SELECT * FROM balance WHERE amount >= ?", [global.support.decimalToCoin(global.config.payout.walletMin)]).then(function (rows) { + console.log("Loaded all payees into the system for processing"); + let paymentDestinations = []; + let totalAmount = 0; + let roundCount = 0; + let payeeList = []; + let payeeObjects = {}; + rows.forEach(function (row) { + debug("Starting round for: " + JSON.stringify(row)); + let payee = new Payee(row.amount, row.payment_address, row.payment_id, row.bitcoin); + payeeObjects[row.payment_address] = payee; + global.mysql.query("SELECT payout_threshold FROM users WHERE username = ?", [payee.id]).then(function (userRow) { + roundCount += 1; + let threshold = 0; + if (userRow.length !== 0) { + threshold = userRow[0].payout_threshold; + } + payee.poolType = row.pool_type; + payee.sqlID = row.id; + if (payee.poolType === "fees" && payee.address === global.config.payout.feeAddress && payee.amount >= ((global.support.decimalToCoin(global.config.payout.feesForTXN) + global.support.decimalToCoin(global.config.payout.exchangeMin)))) { + debug("This is the fee address internal check for value"); + payee.amount -= global.support.decimalToCoin(global.config.payout.feesForTXN); + } else if (payee.address === global.config.payout.feeAddress && payee.poolType === "fees") { + debug("Unable to pay fee address."); + payee.amount = 0; + } + let remainder = payee.amount % (global.config.payout.denom * global.config.general.sigDivisor); + if (remainder !== 0) { + payee.amount -= remainder; + } + if (payee.amount > threshold) { + payee.setFeeAmount(); + if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length !== 106) { + debug("Adding " + payee.id + " to the list of people to pay (OG Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + paymentDestinations.push({amount: payee.amount - payee.fee, address: payee.address}); + totalAmount += payee.amount; + payeeList.push(payee); + } else if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length === 106 && (payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0))) { + // Special code to handle integrated payment addresses. What a pain in the rear. + // These are exchange addresses though, so they need to hit the exchange payout amount. + debug("Adding " + payee.id + " to the list of people to pay (Integrated Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makePaymentAsIntegrated(); + } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 0) { + debug("Adding " + payee.id + " to the list of people to pay (Payment ID Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makePaymentWithID(); + } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 1) { + debug("Adding " + payee.id + " to the list of people to pay (Bitcoin Payout). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makeBitcoinPayment(); + } + } + debug("Went: " + roundCount + " With: " + paymentDestinations.length + " Possible destinations and: " + rows.length + " Rows"); + if (roundCount === rows.length && paymentDestinations.length > 0) { + while (paymentDestinations.length > 0) { + let paymentDetails = { + destinations: paymentDestinations.splice(0, global.config.payout.maxPaymentTxns), + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn + }; + console.log("Paying out: " + paymentDetails.destinations.length + " people"); + paymentQueue.push(paymentDetails, function (body) { //jshint ignore:line + // This is the only section that could potentially contain multiple txns. Lets do this safely eh? + if (body.fee && body.fee > 0.0000001) { + debug("Made it to the SQL insert for transactions"); + let totalAmount = 0; + paymentDetails.destinations.forEach(function (payeeItem) { + totalAmount += payeeObjects[payeeItem.address].amount; + totalAmount += payeeObjects[payeeItem.address].fee; + }); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, null, null, totalAmount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, paymentDetails.destinations.length]).then(function (result) { + paymentDetails.destinations.forEach(function (payeeItem) { + payee = payeeObjects[payeeItem.address]; + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + } + } + }); + }); + }); +} + +function init() { + global.support.rpcWallet("store", [], function () { + }); + if (global.config.allowBitcoin) { + determineBestExchange(); + setInterval(updateXMRToCompletion, 90000); + setInterval(updateShapeshiftCompletion, 90000); + setInterval(determineBestExchange, 60000); + } + setInterval(function () { + global.support.rpcWallet("store", [], function () { + }); + }, 60000); + console.log("Setting the payment timer to: " + global.config.payout.timer + " minutes with a: " + global.config.payout.timerRetry + " minute delay if the wallet is out of money"); + makePayments(); +} + +init(); From 8583bdb24a9557a806f10a4a6a96d992c4a3b688 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Thu, 1 Mar 2018 23:01:07 -0500 Subject: [PATCH 04/22] Create etn.js --- lib/coins/etn.js | 163 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 lib/coins/etn.js diff --git a/lib/coins/etn.js b/lib/coins/etn.js new file mode 100644 index 00000000..d22cd777 --- /dev/null +++ b/lib/coins/etn.js @@ -0,0 +1,163 @@ +"use strict"; +const bignum = require('bignum'); +const cnUtil = require('cryptonote-util'); +const multiHashing = require('multi-hashing'); +const crypto = require('crypto'); +const debug = require('debug')('coinFuncs'); + +let hexChars = new RegExp("[0-9a-f]+"); + +function Coin(data){ + this.bestExchange = global.config.payout.bestExchange; + this.data = data; + let instanceId = crypto.randomBytes(4); + //Edit coin and pool donate to etn-pool.org =) + this.coinDevAddress = "etnk972DGajHFYWAnPkMBb88apD6XgdoWAciLHw6w4ZVBapaxMRSxzsjMUjX6ZX7he4fseqMQP3LZV5WS5cyiyRn6ws2VT8sgh"; // Developer Address + this.poolDevAddress = "etnk972DGajHFYWAnPkMBb88apD6XgdoWAciLHw6w4ZVBapaxMRSxzsjMUjX6ZX7he4fseqMQP3LZV5WS5cyiyRn6ws2VT8sgh"; // ArqTras PoolDev Address + + this.blockedAddresses = [ + this.coinDevAddress, + this.poolDevAddress + ]; + + this.exchangeAddresses = [ + "etnjzKFU6ogESSKRZZbdqraPdcKVxEC17Cm1Xvbyy76PARQMmgrgceH4krAH6xmjKwJ3HtSAKuyFm1BBWYqtchtq9tBap8Qr4M", //Cryptopia + ]; // These are addresses that MUST have a paymentID to perform logins with. + + this.prefix = 18018; + this.intPrefix = 18019; + + if (global.config.general.testnet === true){ + this.prefix = 18018; + this.intPrefix = 18019; + } + + this.supportsAutoExchange = false; + + this.niceHashDiff = 400000; + + this.getBlockHeaderByID = function(blockId, callback){ + global.support.rpcDaemon('getblockheaderbyheight', {"height": blockId}, function (body) { + if (body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockHeaderByHash = function(blockHash, callback){ + global.support.rpcDaemon('getblockheaderbyhash', {"hash": blockHash}, function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getLastBlockHeader = function(callback){ + global.support.rpcDaemon('getlastblockheader', [], function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockTemplate = function(walletAddress, callback){ + global.support.rpcDaemon('getblocktemplate', { + reserve_size: 17, + wallet_address: walletAddress + }, function(body){ + return callback(body); + }); + }; + + this.baseDiff = function(){ + return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); + }; + + this.validateAddress = function(address){ + // This function should be able to be called from the async library, as we need to BLOCK ever so slightly to verify the address. + address = new Buffer(address); + if (cnUtil.address_decode(address) === this.prefix){ + return true; + } + return cnUtil.address_decode_integrated(address) === this.intPrefix; + }; + + this.convertBlob = function(blobBuffer){ + return cnUtil.convert_blob(blobBuffer); + }; + + this.constructNewBlob = function(blockTemplate, NonceBuffer){ + return cnUtil.construct_block_blob(blockTemplate, NonceBuffer); + }; + + this.getBlockID = function(blockBuffer){ + return cnUtil.get_block_id(blockBuffer); + }; + + this.BlockTemplate = function(template) { + /* + Generating a block template is a simple thing. Ask for a boatload of information, and go from there. + Important things to consider. + The reserved space is 13 bytes long now in the following format: + Assuming that the extraNonce starts at byte 130: + |130-133|134-137|138-141|142-145| + |minerNonce/extraNonce - 4 bytes|instanceId - 4 bytes|clientPoolNonce - 4 bytes|clientNonce - 4 bytes| + This is designed to allow a single block template to be used on up to 4 billion poolSlaves (clientPoolNonce) + Each with 4 billion clients. (clientNonce) + While being unique to this particular pool thread (instanceId) + With up to 4 billion clients (minerNonce/extraNonce) + Overkill? Sure. But that's what we do here. Overkill. + */ + + // Set this.blob equal to the BT blob that we get from upstream. + this.blob = template.blocktemplate_blob; + this.idHash = crypto.createHash('md5').update(template.blocktemplate_blob).digest('hex'); + // Set this.diff equal to the known diff for this block. + this.difficulty = template.difficulty; + // Set this.height equal to the known height for this block. + this.height = template.height; + // Set this.reserveOffset to the byte location of the reserved offset. + this.reserveOffset = template.reserved_offset; + // Set this.buffer to the binary decoded version of the BT blob. + this.buffer = new Buffer(this.blob, 'hex'); + // Copy the Instance ID to the reserve offset + 4 bytes deeper. Copy in 4 bytes. + instanceId.copy(this.buffer, this.reserveOffset + 4, 0, 3); + // Generate a clean, shiny new buffer. + this.previous_hash = new Buffer(32); + // Copy in bytes 7 through 39 to this.previous_hash from the current BT. + this.buffer.copy(this.previous_hash, 0, 7, 39); + // Reset the Nonce. - This is the per-miner/pool nonce + this.extraNonce = 0; + // The clientNonceLocation is the location at which the client pools should set the nonces for each of their clients. + this.clientNonceLocation = this.reserveOffset + 12; + // The clientPoolLocation is for multi-thread/multi-server pools to handle the nonce for each of their tiers. + this.clientPoolLocation = this.reserveOffset + 8; + this.nextBlob = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Convert the blob into something hashable. + return global.coinFuncs.convertBlob(this.buffer).toString('hex'); + }; + // Make it so you can get the raw block blob out. + this.nextBlobWithChildNonce = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Don't convert the blob to something hashable. You bad. + return this.buffer.toString('hex'); + }; + }; + + this.cryptoNight = multiHashing.cryptonight; + +} + +module.exports = Coin; From dfff843b65f2a975d1e09cde16dacc46acc35ab4 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 10:48:19 -0500 Subject: [PATCH 05/22] Create sumo.js --- lib/payment_systems/sumo.js | 696 ++++++++++++++++++++++++++++++++++++ 1 file changed, 696 insertions(+) create mode 100644 lib/payment_systems/sumo.js diff --git a/lib/payment_systems/sumo.js b/lib/payment_systems/sumo.js new file mode 100644 index 00000000..9b3fb3bb --- /dev/null +++ b/lib/payment_systems/sumo.js @@ -0,0 +1,696 @@ +"use strict"; +const shapeshift = require('shapeshift.io'); +const async = require("async"); +const debug = require("debug")("payments"); +const request = require('request-json'); +const range = require('range'); + +let hexChars = new RegExp("[0-9a-f]+"); +let bestExchange = global.config.payout.bestExchange; +let xmrAPIClient = request.createClient('https://xmr.to/api/v1/xmr2btc/'); +let extraPaymentRound = false; +let paymentTimer = null; + +let shapeshiftQueue = async.queue(function (task, callback) { + // Amount needs to be shifted in as a non-completed value, as the wallet will only take non-complete values.. + let amount = task.amount - task.fee; + // Address is the destination address IN BTC. + let address = task.address; + // PaymentIDs are the paymentID's to flag as paid by this transaction. + // Should be a massive list of ID's so we can bulk-update them, by merging them with 's. + // Here we go! General process: Scan shapeshift for valid amounts of funds to xfer around. + // Once there's enough funds, then we active txn + // Do a wallet call to xfer. + // Setup a monitor on the transaction + async.waterfall([ + function (intCallback) { + // Verify if the coin is active in ShapeShift first. + shapeshift.coins(function (err, coinData) { + if (err) { + intCallback(err); + } else if (!coinData.hasOwnProperty(global.config.general.coinCode) || coinData[global.config.general.coinCode].status !== "available") { + intCallback("Coin " + global.config.general.coinCode + " Is not available at this time on shapeshift."); + } else { + intCallback(null); + } + }); + }, + function (intCallback) { + // Get the market information from shapeshift, which includes deposit limits, minimum deposits, rates, etc. + shapeshift.marketInfo(global.config.payout.shapeshiftPair, function (err, marketInfo) { + if (err) { + intCallback(err); + } else if (!marketInfo.hasOwnProperty("limit") || marketInfo.limit <= global.support.coinToDecimal(amount)) { + intCallback("Not enough coin in shapeshift to process at this time."); + } else if (!marketInfo.hasOwnProperty("min") || marketInfo.min >= global.support.coinToDecimal(amount)) { + intCallback("Not enough coin to hit the shapeshift minimum deposits."); + } else { + intCallback(null, marketInfo); + } + }); + }, + function (marketInfo, intCallback) { + // Validated there's enough coin. Time to make our dank txn. + // Return: + /* + { + "orderId": "cc49c556-e645-4c15-a943-d50a935274e4", + "sAddress": "46yzCCD3Mza9tRj7aqPSaxVbbePtuAeKzf8Ky2eRtcXGcEgCg1iTBio6N4sPmznfgGEUGDoBz5CLxZ2XPTyZu1yoCAG7zt6", + "deposit": "d8041668718e6e9d9d0fd335ee5ecd923e6fd074c41316d041cc18b779ade10e", + "depositType": "XMR", + "withdrawal": "1DbxcoCBSA9N7uZvkcvWxuLxSau9q9Pwiu", + "withdrawalType": "BTC", + "public": null, + "apiPubKey": "shapeshift", + "returnAddress": "46XWBqE1iwsVxSDP1qDrxhE1XvsZV6eALG5LwnoMdjbT4GPdy2bZTb99kagzxp2MMjUamTYZ4WgvZdFadvMimTjvR6Gv8hL", + "returnAddressType": "XMR" + } + Valid Statuses: + "received" + "complete" + "error" + "no_deposits" + Complete State Information: + { + "status": "complete", + "address": "d8041668718e6e9d9d0fd335ee5ecd923e6fd074c41316d041cc18b779ade10e", + "withdraw": "1DbxcoCBSA9N7uZvkcvWxuLxSau9q9Pwiu", + "incomingCoin": 3, + "incomingType": "XMR", + "outgoingCoin": "0.04186155", + "outgoingType": "BTC", + "transaction": "be9d97f6fc75262151f8f63e035c6ed638b9eb2a4e93fef43ea63124b045dbfb" + } + */ + shapeshift.shift(address, global.config.payout.shapeshiftPair, {returnAddress: global.config.pool.address}, function (err, returnData) { + if (err) { + intCallback(err); + } else { + global.mysql.query("INSERT INTO shapeshiftTxn (id, address, paymentID, depositType, withdrawl, withdrawlType, returnAddress, returnAddressType, txnStatus) VALUES (?,?,?,?,?,?,?,?,?)", + [returnData.orderId, returnData.sAddress, returnData.deposit, returnData.depositType, returnData.withdrawl, returnData.withdrawlType, returnData.returnAddress, returnData.returnAddressType, 'no_deposits']).then(function () { + intCallback(null, marketInfo, returnData); + }).catch(function (error) { + intCallback(error); + }); + } + }); + }, + function (marketInfo, shapeshiftTxnData, intCallback) { + // Make the payment to ShapeShift + let paymentDetails = { + destinations: [ + { + amount: amount, + address: shapeshiftTxnData.sAddress + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn, + payment_id: shapeshiftTxnData.deposit + }; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + intCallback(null, marketInfo, shapeshiftTxnData, body); + } else { + intCallback("Unknown error from the wallet."); + } + }); + }, + function (marketInfo, shapeshiftTxnData, body, intCallback) { + // body.tx_hash = XMR transaction hash. + // Need to add transaction. + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees, exchange_rate, exchange_name, exchange_txn_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + [1, address, null, task.amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, global.support.decimalToCoin(marketInfo.minerFee), 1, global.support.decimalToCoin(marketInfo.rate), 'shapeshift', shapeshiftTxnData.orderId]).then(function (result) { + intCallback(null, result.insertId); + }).catch(function (error) { + intCallback(error); + }); + } + ], function (err, result) { + if (err) { + console.error("Error processing shapeshift txn: " + JSON.stringify(err)); + callback(true); + } else { + // Need to fill out this data pronto! + console.log("Processed ShapeShift transaction for: " + address + " Paid out: " + result + " payments in the db"); + callback(null, result); + } + }); +}, 2); + +let xmrToQueue = async.queue(function (task, callback) { + // http://xmrto-api.readthedocs.io/en/latest/introduction.html + // Documentation looks good! + // Amount needs to be shifted in as a non-completed value, as the wallet will only take non-complete values.. + let amount = task.amount - task.fee; + // Address is the destination address IN BTC. + let address = task.address; + // PaymentIDs are the paymentID's to flag as paid by this transaction. + // Should be a massive list of ID's so we can bulk-update them, by merging them with 's. + // Here we go! General process: Scan shapeshift for valid amounts of funds to xfer around. + // Once there's enough funds, then we active txn + // Do a wallet call to xfer. + // Setup a monitor on the transaction + async.waterfall([ + function (intCallback) { + // Verify if XMR.to is ready to get to work. + xmrAPIClient.get('order_parameter_query/', function (err, res, body) { + if (err) { + return intCallback(err); + } else if (body.error_msg) { + return intCallback(body.error_msg); + } else { + let amtOfBTC = ((amount / global.config.general.sigDivisor) * body.price).toPrecision(5); + console.log("Attempting to pay: " + address + " Amount: " + amtOfBTC + " BTC or " + amount / global.config.general.sigDivisor + " XMR"); + console.log("Response from XMR.to: " + JSON.stringify(body)); + if (body.lower_limit >= amtOfBTC) { + return intCallback("Not enough XMR to hit the minimum deposit"); + } else if (body.upper_limit <= amtOfBTC) { + return intCallback("Too much XMR to pay out to xmr.to"); + } else { + return intCallback(null, amtOfBTC); + } + } + }); + }, + function (btcValue, intCallback) { + // Validated there's enough coin. Time to make our dank txn. + // Return: + /* + { + "state": "TO_BE_CREATED", + "btc_amount": , + "btc_dest_address": "", + "uuid": "" + } + Valid Statuses: + "TO_BE_CREATED" + "UNPAID" + "UNDERPAID" + "PAID_UNCONFIRMED" + "PAID" + "BTC_SENT" + "TIMED_OUT" + "NOT_FOUND" + // Create, then immediately update with the new information w/ a status call. + */ + console.log("Amount of BTC to pay: " + btcValue); + xmrAPIClient.post('order_create/', { + btc_amount: btcValue, + btc_dest_address: address + }, function (err, res, body) { + if (err) { + return intCallback(err); + } else if (body.error_msg) { + return intCallback(body.error_msg); + } else { + return intCallback(null, body.uuid); + } + }); + }, + function (txnID, intCallback) { + // This function only exists because xmr.to is a pretty little fucking princess. + async.doUntil(function (xmrCallback) { + xmrAPIClient.post('order_status_query/', {uuid: txnID}, function (err, res, body) { + if (err) { + return intCallback(err); + } else if (body.error_msg) { + return intCallback(body.error_msg); + } else { + xmrCallback(null, body.state); + } + }); + }, + function (xmrCallback) { + return xmrCallback !== "TO_BE_CREATED"; + }, + function () { + intCallback(null, txnID); + }); + }, + function (txnID, intCallback) { + xmrAPIClient.post('order_status_query/', {uuid: txnID}, function (err, res, body) { + if (err) { + return intCallback(err); + } else if (body.error_msg) { + return intCallback(body.error_msg); + } else { + console.log(JSON.stringify(body)); + global.mysql.query("INSERT INTO xmrtoTxn (id, address, paymentID, depositType, withdrawl, withdrawlType, returnAddress, returnAddressType, txnStatus, amountDeposited, amountSent) VALUES (?,?,?,?,?,?,?,?,?,?,?)", + [txnID, body.xmr_receiving_address, body.xmr_required_payment_id_long, 'XMR', body.btc_dest_address, 'BTC', global.config.pool.address, 'XMR', body.state_str, global.support.decimalToCoin(body.xmr_amount_total), global.support.decimalToCoin(body.btc_amount)]).then(function () { + return intCallback(null, body, global.support.decimalToCoin(body.xmr_amount_total)); + }).catch(function (error) { + return intCallback(error); + }); + } + }); + }, + function (orderStatus, xmrDeposit, intCallback) { + // Make the payment to ShapeShift + let paymentDetails = { + destinations: [ + { + amount: xmrDeposit, + address: orderStatus.xmr_receiving_address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn, + payment_id: orderStatus.xmr_required_payment_id_long + }; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + return intCallback(null, orderStatus, body); + } else { + return intCallback("Unknown error from the wallet."); + } + }); + }, + function (orderStatus, body, intCallback) { + // body.tx_hash = XMR transaction hash. + // Need to add transaction. + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees, exchange_rate, exchange_name, exchange_txn_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + [1, address, null, global.support.decimalToCoin(orderStatus.xmr_amount_total), body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1, global.support.decimalToCoin(orderStatus.xmr_price_btc), 'xmrto', orderStatus.uuid]).then(function (result) { + return intCallback(null, result.insertId); + }).catch(function (error) { + return intCallback(error); + }); + } + ], function (err, result) { + if (err) { + console.error("Error processing XMRTo txn: " + JSON.stringify(err)); + return callback("Error!"); + } else { + // Need to fill out this data pronto! + console.log("Processed XMRTo transaction for: " + address + " Paid out: " + result + " payments in the db"); + return callback(null, result); + } + }); +}, 2); + +let paymentQueue = async.queue(function (paymentDetails, callback) { + /* + support JSON URI: http://10.0.0.2:28082/json_rpc Args: {"id":"0","jsonrpc":"2.0","method":"transfer","params":{"destinations":[{"amount":68130252045355,"address":"A2MSrn49ziBPJBh8ZNEhhbfyLMou6mao4C1F5TLGUatmUnCxZArDYkcbAnVkVEopWVeak2rKDrmc8JpoS7n5dvfN9YDPBTG"}],"mixin":4,"payment_id":"7e52c5266de9fede7fb3abc0cd88f937b38b51426f7b34ff99729d28ce4e1142"}} +1ms + payments Payment made: {"id":"0","jsonrpc":"2.0","result":{"fee":40199391255,"tx_hash":"c418708643f72635edf522490bfb2cae9d42a6dc1df30dcde844862dfd88f5b3","tx_key":""}} +2s + */ + if (paymentTimer !== null){ + clearInterval(paymentTimer); + paymentTimer = null; + } + debug("Making payment based on: " + JSON.stringify(paymentDetails)); + let transferFunc = 'transfer'; + global.support.rpcWallet(transferFunc, paymentDetails, function (body) { + debug("Payment made: " + JSON.stringify(body)); + if (body.hasOwnProperty('error')) { + if (body.error.message === "not enough money"){ + console.error("Issue making payments, not enough money, will try later"); + if(!extraPaymentRound){ + setTimeout(function(){ + makePayments(); + }, global.config.payout.timerRetry * 60 * 1000); + } + extraPaymentRound = true; + return callback(false); + } else { + console.error("Issue making payments" + JSON.stringify(body.error)); + console.error("Will not make more payments until the payment daemon is restarted!"); + //toAddress, subject, body + global.support.sendEmail(global.config.general.adminEmail, "Payment daemon unable to make payment", + "Hello,\r\nThe payment daemon has hit an issue making a payment: " + JSON.stringify(body.error) + + ". Please investigate and restart the payment daemon as appropriate"); + return; + } + } + if (paymentDetails.hasOwnProperty('payment_id')) { + console.log("Payment made to " + paymentDetails.destinations[0].address + " with PaymentID: " + paymentDetails.payment_id + " For: " + global.support.coinToDecimal(paymentDetails.destinations[0].amount) + " XMR with a " + global.support.coinToDecimal(body.result.fee) + " XMR Mining Fee"); + return callback(body.result); + } else { + if (transferFunc === 'transfer') { + console.log("Payment made out to multiple people, total fee: " + global.support.coinToDecimal(body.result.fee) + " XMR"); + } + let intCount = 0; + paymentDetails.destinations.forEach(function (details) { + console.log("Payment made to: " + details.address + " For: " + global.support.coinToDecimal(details.amount) + " XMR"); + intCount += 1; + if (intCount === paymentDetails.destinations.length) { + return callback(body.result); + } + }); + } + }); +}, 1); + +paymentQueue.drain = function(){ + extraPaymentRound = false; + if (global.config.payout.timer > 35791){ + console.error("Payout timer is too high. Please use a value under 35791 to avoid overflows."); + } else { + paymentTimer = setInterval(makePayments, global.config.payout.timer * 60 * 1000); + } + global.database.setCache('lastPaymentCycle', Math.floor(Date.now()/1000)); +}; + +function updateShapeshiftCompletion() { + global.mysql.query("SELECT * FROM shapeshiftTxn WHERE txnStatus NOT IN ('complete', 'error')").then(function (rows) { + rows.forEach(function (row) { + shapeshift.status(row.paymentID, function (err, status, returnData) { + if (err) { + return; + } + global.mysql.query("UPDATE shapeshiftTxn SET txnStatus = ? WHERE id = ?", [status, row.id]).then(function () { + if (status === 'complete') { + global.mysql.query("UPDATE shapeshiftTxn SET amountDeposited = ?, amountSent = ?, transactionHash = ? WHERE id = ?", + [global.support.decimalToCoin(returnData.incomingCoin), global.support.bitcoinDecimalToCoin(returnData.outgoingCoin), returnData.transaction, row.id]).then(function () { + global.mysql.query("UPDATE transactions SET confirmed = 1, confirmed_time = now(), btc_amt = ? WHERE exchange_txn_id = ?", [global.support.bitcoinDecimalToCoin(returnData.outgoingCoin), row.id]); + }); + } else if (status === 'error') { + // Failed txn. Need to rollback and delete all related data. Here we go! + global.mysql.query("DELETE FROM shapeshiftTxn WHERE id = ?", [row.id]); + global.mysql.query("SELECT id, xmr_amt, address FROM transactions WHERE exchange_txn_id = ?", [row.id]).then(function (rows) { + global.mysql.query("DELETE FROM transactions WHERE id = ?", [rows[0].id]); + global.mysql.query("DELETE payments WHERE transaction_id = ?", [rows[0].id]); + global.mysql.query("UPDATE balance SET amount = amount+? WHERE payment_address = ? limit 1", [rows[0].xmr_amt, rows[0].address]); + }); + console.error("Failed transaction from ShapeShift " + JSON.stringify(returnData)); + } + }); + }); + }); + }); +} + +function updateXMRToCompletion() { + global.mysql.query("SELECT * FROM xmrtoTxn WHERE txnStatus NOT IN ('PAID', 'TIMED_OUT', 'NOT_FOUND', 'BTC_SENT')").then(function (rows) { + rows.forEach(function (row) { + xmrAPIClient.post('order_status_query/', {uuid: row.id}, function (err, res, body) { + if (err) { + console.log("Error in getting order status: " + JSON.stringify(err)); + return; + } + if (body.error_msg) { + console.log("Error in getting order status: " + body.error_msg); + return; + } + global.mysql.query("UPDATE xmrtoTxn SET txnStatus = ? WHERE id = ?", [body.state, row.id]).then(function () { + if (body.status === 'BTC_SENT') { + global.mysql.query("UPDATE xmrtoTxn SET transactionHash = ? WHERE id = ?", [body.btc_transaction_id, row.id]).then(function () { + global.mysql.query("UPDATE transactions SET confirmed = 1, confirmed_time = now(), btc_amt = ? WHERE exchange_txn_id = ?", [global.support.bitcoinDecimalToCoin(body.btc_amount), row.id]); + }); + } else if (body.status === 'TIMED_OUT' || body.status === 'NOT_FOUND') { + global.mysql.query("DELETE FROM xmrtoTxn WHERE id = ?", [row.id]); + global.mysql.query("SELECT id, xmr_amt, address FROM transactions WHERE exchange_txn_id = ?", [row.id]).then(function (rows) { + global.mysql.query("DELETE FROM transactions WHERE id = ?", [rows[0].id]); + global.mysql.query("DELETE payments WHERE transaction_id = ?", [rows[0].id]); + global.mysql.query("UPDATE balance SET amount = amount+? WHERE payment_address = ? limit 1", [rows[0].xmr_amt, rows[0].address]); + }); + console.error("Failed transaction from XMRto " + JSON.stringify(body)); + } + }); + }); + }); + }); +} + +function determineBestExchange() { + async.waterfall([ + function (callback) { + // Verify if the coin is active in ShapeShift first. + shapeshift.coins(function (err, coinData) { + if (err) { + return callback(err); + } else if (!coinData.hasOwnProperty(global.config.general.coinCode) || coinData[global.config.general.coinCode].status !== "available") { + return callback("Coin " + global.config.general.coinCode + " Is not available at this time on shapeshift."); + } else { + return callback(null); + } + }); + }, + function (callback) { + // Get the market information from shapeshift, which includes deposit limits, minimum deposits, rates, etc. + shapeshift.marketInfo(global.config.payout.shapeshiftPair, function (err, marketInfo) { + if (err) { + return callback(err); + } else if (!marketInfo.hasOwnProperty("rate")) { + return callback("Shapeshift did not return the rate."); + } else { + return callback(null, global.support.bitcoinDecimalToCoin(marketInfo.rate)); + } + }); + }, + function (ssValue, callback) { + xmrAPIClient.get('order_parameter_query/', function (err, res, body) { + console.log("XMR.to pricing body: " + JSON.stringify(body)); + if (err) { + return callback(err); + } else if (body.error_msg) { + return callback(body.error_msg); + } else { + return callback(null, ssValue, global.support.bitcoinDecimalToCoin(body.price)); + } + }); + } + ], function (err, ssValue, xmrToValue) { + if (err) { + return console.error("Error processing exchange value: " + JSON.stringify(err)); + } + debug("ShapeShift Value: " + global.support.bitcoinCoinToDecimal(ssValue) + " XMR.to Value: " + global.support.bitcoinCoinToDecimal(xmrToValue)); + if (ssValue >= xmrToValue) { + console.log("ShapeShift is the better BTC exchange, current rate: " + global.support.bitcoinCoinToDecimal(ssValue)); + bestExchange = 'shapeshift'; + global.mysql.query("UPDATE config SET item_value = 'shapeshift' where item='bestExchange'"); + global.mysql.query("UPDATE config SET item_value = ? where item='exchangeRate'", [ssValue]); + } else { + console.log("XMR.to is the better BTC exchange, current rate: " + global.support.bitcoinCoinToDecimal(xmrToValue)); + bestExchange = 'xmrto'; + global.mysql.query("UPDATE config SET item_value = 'xmrto' where item='bestExchange'"); + global.mysql.query("UPDATE config SET item_value = ? where item='exchangeRate'", [xmrToValue]); + } + }); +} + +function Payee(amount, address, paymentID, bitcoin) { + this.amount = amount; + this.address = address; + this.paymentID = paymentID; + this.bitcoin = bitcoin; + this.blockID = 0; + this.poolType = ''; + this.transactionID = 0; + this.sqlID = 0; + if (paymentID === null) { + this.id = address; + } else { + this.id = address + "." + paymentID; + } + this.fee = 0; + this.baseFee = global.support.decimalToCoin(global.config.payout.feeSlewAmount); + this.setFeeAmount = function () { + if (this.amount <= global.support.decimalToCoin(global.config.payout.walletMin)) { + this.fee = this.baseFee; + } else if (this.amount <= global.support.decimalToCoin(global.config.payout.feeSlewEnd)) { + let feeValue = this.baseFee / (global.support.decimalToCoin(global.config.payout.feeSlewEnd) - global.support.decimalToCoin(global.config.payout.walletMin)); + this.fee = this.baseFee - ((this.amount - global.support.decimalToCoin(global.config.payout.walletMin)) * feeValue); + } + this.fee = Math.floor(this.fee); + }; + + this.makePaymentWithID = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn, + payment_id: this.paymentID + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let paymentID = this.paymentID; + let payee = this; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, address, paymentID, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.makePaymentAsIntegrated = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let payee = this; + + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?)", + [0, address, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.makeBitcoinPayment = function () { + let functionalData = {address: this.address, amount: this.amount, fee: this.fee}; + let payee = this; + if (bestExchange === 'xmrto') { + xmrToQueue.push(functionalData, function (err, transactionID) { + if (err) { + return console.error("Error processing payment for " + functionalData.address); + } + payee.transactionID = transactionID; + payee.trackPayment(); + }); + } else { + shapeshiftQueue.push(functionalData, function (err, transactionID) { + if (err) { + return console.error("Error processing payment for " + functionalData.address); + } + payee.transactionID = transactionID; + payee.trackPayment(); + }); + } + }; + + this.trackPayment = function () { + global.mysql.query("UPDATE balance SET amount = amount - ? WHERE id = ?", [this.amount, this.sqlID]); + global.mysql.query("INSERT INTO payments (unlocked_time, paid_time, pool_type, payment_address, transaction_id, bitcoin, amount, payment_id, transfer_fee)" + + " VALUES (now(), now(), ?, ?, ?, ?, ?, ?, ?)", [this.poolType, this.address, this.transactionID, this.bitcoin, this.amount - this.fee, this.paymentID, this.fee]); + }; +} + +function makePayments() { + global.mysql.query("SELECT * FROM balance WHERE amount >= ?", [global.support.decimalToCoin(global.config.payout.walletMin)]).then(function (rows) { + console.log("Loaded all payees into the system for processing"); + let paymentDestinations = []; + let totalAmount = 0; + let roundCount = 0; + let payeeList = []; + let payeeObjects = {}; + rows.forEach(function (row) { + debug("Starting round for: " + JSON.stringify(row)); + let payee = new Payee(row.amount, row.payment_address, row.payment_id, row.bitcoin); + payeeObjects[row.payment_address] = payee; + global.mysql.query("SELECT payout_threshold FROM users WHERE username = ?", [payee.id]).then(function (userRow) { + roundCount += 1; + let threshold = 0; + if (userRow.length !== 0) { + threshold = userRow[0].payout_threshold; + } + payee.poolType = row.pool_type; + payee.sqlID = row.id; + if (payee.poolType === "fees" && payee.address === global.config.payout.feeAddress && payee.amount >= ((global.support.decimalToCoin(global.config.payout.feesForTXN) + global.support.decimalToCoin(global.config.payout.exchangeMin)))) { + debug("This is the fee address internal check for value"); + payee.amount -= global.support.decimalToCoin(global.config.payout.feesForTXN); + } else if (payee.address === global.config.payout.feeAddress && payee.poolType === "fees") { + debug("Unable to pay fee address."); + payee.amount = 0; + } + let remainder = payee.amount % (global.config.payout.denom * global.config.general.sigDivisor); + if (remainder !== 0) { + payee.amount -= remainder; + } + if (payee.amount > threshold) { + payee.setFeeAmount(); + if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length !== 106) { + debug("Adding " + payee.id + " to the list of people to pay (OG Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + paymentDestinations.push({amount: payee.amount - payee.fee, address: payee.address}); + totalAmount += payee.amount; + payeeList.push(payee); + } else if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length === 106 && (payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0))) { + // Special code to handle integrated payment addresses. What a pain in the rear. + // These are exchange addresses though, so they need to hit the exchange payout amount. + debug("Adding " + payee.id + " to the list of people to pay (Integrated Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makePaymentAsIntegrated(); + } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 0) { + debug("Adding " + payee.id + " to the list of people to pay (Payment ID Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makePaymentWithID(); + } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 1) { + debug("Adding " + payee.id + " to the list of people to pay (Bitcoin Payout). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makeBitcoinPayment(); + } + } + debug("Went: " + roundCount + " With: " + paymentDestinations.length + " Possible destinations and: " + rows.length + " Rows"); + if (roundCount === rows.length && paymentDestinations.length > 0) { + while (paymentDestinations.length > 0) { + let paymentDetails = { + destinations: paymentDestinations.splice(0, global.config.payout.maxPaymentTxns), + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn + }; + console.log("Paying out: " + paymentDetails.destinations.length + " people"); + paymentQueue.push(paymentDetails, function (body) { //jshint ignore:line + // This is the only section that could potentially contain multiple txns. Lets do this safely eh? + if (body.fee && body.fee > 10) { + debug("Made it to the SQL insert for transactions"); + let totalAmount = 0; + paymentDetails.destinations.forEach(function (payeeItem) { + totalAmount += payeeObjects[payeeItem.address].amount; + totalAmount += payeeObjects[payeeItem.address].fee; + }); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, null, null, totalAmount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, paymentDetails.destinations.length]).then(function (result) { + paymentDetails.destinations.forEach(function (payeeItem) { + payee = payeeObjects[payeeItem.address]; + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + } + } + }); + }); + }); +} + +function init() { + global.support.rpcWallet("store", [], function () { + }); + if (global.config.allowBitcoin) { + determineBestExchange(); + setInterval(updateXMRToCompletion, 90000); + setInterval(updateShapeshiftCompletion, 90000); + setInterval(determineBestExchange, 60000); + } + setInterval(function () { + global.support.rpcWallet("store", [], function () { + }); + }, 60000); + console.log("Setting the payment timer to: " + global.config.payout.timer + " minutes with a: " + global.config.payout.timerRetry + " minute delay if the wallet is out of money"); + makePayments(); +} + +init(); From 4e5a1ca0fa9c55b5995eb30db939dbc36ccc4126 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 10:48:54 -0500 Subject: [PATCH 06/22] Create sumo.js --- lib/coins/sumo.js | 162 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 lib/coins/sumo.js diff --git a/lib/coins/sumo.js b/lib/coins/sumo.js new file mode 100644 index 00000000..dab0a5cf --- /dev/null +++ b/lib/coins/sumo.js @@ -0,0 +1,162 @@ +"use strict"; +const bignum = require('bignum'); +const cnUtil = require('cryptonote-util'); +const multiHashing = require('multi-hashing'); +const crypto = require('crypto'); +const debug = require('debug')('coinFuncs'); + +let hexChars = new RegExp("[0-9a-f]+"); + +function Coin(data){ + this.bestExchange = global.config.payout.bestExchange; + this.data = data; + let instanceId = crypto.randomBytes(4); + this.coinDevAddress = ""; // Developer Address + this.poolDevAddress = ""; // Snipa Address + + this.blockedAddresses = [ + + ]; + + this.exchangeAddresses = [ + + ]; // These are addresses that MUST have a paymentID to perform logins with. + + //this.prefix = cnUtil.address_decode(new Buffer(config.poolServer.poolAddress)) + this.prefix = 9241; + this.intPrefix = 28822; //cnUtil.address_decode_integrated(new Buffer(config.poolServer.poolAddress)); + + if (global.config.general.testnet === true){ + this.prefix = 53; + this.intPrefix = 54; + } + + this.supportsAutoExchange = true; + + this.niceHashDiff = 400000; + + this.getBlockHeaderByID = function(blockId, callback){ + global.support.rpcDaemon('getblockheaderbyheight', {"height": blockId}, function (body) { + if (body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockHeaderByHash = function(blockHash, callback){ + global.support.rpcDaemon('getblockheaderbyhash', {"hash": blockHash}, function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getLastBlockHeader = function(callback){ + global.support.rpcDaemon('getlastblockheader', [], function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockTemplate = function(walletAddress, callback){ + global.support.rpcDaemon('getblocktemplate', { + reserve_size: 17, + wallet_address: walletAddress + }, function(body){ + return callback(body); + }); + }; + + this.baseDiff = function(){ + return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); + }; + + this.validateAddress = function(address){ + // This function should be able to be called from the async library, as we need to BLOCK ever so slightly to verify the address. + address = new Buffer(address); + if (cnUtil.address_decode(address) === this.prefix){ + return true; + } + return cnUtil.address_decode_integrated(address) === this.intPrefix; + }; + + this.convertBlob = function(blobBuffer){ + return cnUtil.convert_blob(blobBuffer); + }; + + this.constructNewBlob = function(blockTemplate, NonceBuffer){ + return cnUtil.construct_block_blob(blockTemplate, NonceBuffer); + }; + + this.getBlockID = function(blockBuffer){ + return cnUtil.get_block_id(blockBuffer); + }; + + this.BlockTemplate = function(template) { + /* + Generating a block template is a simple thing. Ask for a boatload of information, and go from there. + Important things to consider. + The reserved space is 13 bytes long now in the following format: + Assuming that the extraNonce starts at byte 130: + |130-133|134-137|138-141|142-145| + |minerNonce/extraNonce - 4 bytes|instanceId - 4 bytes|clientPoolNonce - 4 bytes|clientNonce - 4 bytes| + This is designed to allow a single block template to be used on up to 4 billion poolSlaves (clientPoolNonce) + Each with 4 billion clients. (clientNonce) + While being unique to this particular pool thread (instanceId) + With up to 4 billion clients (minerNonce/extraNonce) + Overkill? Sure. But that's what we do here. Overkill. + */ + + // Set this.blob equal to the BT blob that we get from upstream. + this.blob = template.blocktemplate_blob; + this.idHash = crypto.createHash('md5').update(template.blocktemplate_blob).digest('hex'); + // Set this.diff equal to the known diff for this block. + this.difficulty = template.difficulty; + // Set this.height equal to the known height for this block. + this.height = template.height; + // Set this.reserveOffset to the byte location of the reserved offset. + this.reserveOffset = template.reserved_offset; + // Set this.buffer to the binary decoded version of the BT blob. + this.buffer = new Buffer(this.blob, 'hex'); + // Copy the Instance ID to the reserve offset + 4 bytes deeper. Copy in 4 bytes. + instanceId.copy(this.buffer, this.reserveOffset + 4, 0, 3); + // Generate a clean, shiny new buffer. + this.previous_hash = new Buffer(32); + // Copy in bytes 7 through 39 to this.previous_hash from the current BT. + this.buffer.copy(this.previous_hash, 0, 7, 39); + // Reset the Nonce. - This is the per-miner/pool nonce + this.extraNonce = 0; + // The clientNonceLocation is the location at which the client pools should set the nonces for each of their clients. + this.clientNonceLocation = this.reserveOffset + 12; + // The clientPoolLocation is for multi-thread/multi-server pools to handle the nonce for each of their tiers. + this.clientPoolLocation = this.reserveOffset + 8; + this.nextBlob = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Convert the blob into something hashable. + return global.coinFuncs.convertBlob(this.buffer).toString('hex'); + }; + // Make it so you can get the raw block blob out. + this.nextBlobWithChildNonce = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Don't convert the blob to something hashable. You bad. + return this.buffer.toString('hex'); + }; + }; + + this.cryptoNight = multiHashing.cryptonight; + +} + +module.exports = Coin; From 7689d1a6cd3ac8c64fe6e6ab32049b81f5ac9fe7 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 11:01:40 -0500 Subject: [PATCH 07/22] Update coinConfig.json --- coinConfig.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/coinConfig.json b/coinConfig.json index e1595a75..80a201ad 100644 --- a/coinConfig.json +++ b/coinConfig.json @@ -17,6 +17,30 @@ "mixIn": 4, "shortCode": "KRB" }, + "etn": { + "funcFile": "./lib/coins/etn.js", + "paymentFile": "./payment_systems/etn.js", + "sigDigits": 100, + "name": "Electroneum", + "mixIn": 0, + "shortCode": "ETN" + }, + "stl": { + "funcFile": "./lib/coins/stl.js", + "paymentFile": "./payment_systems/stl.js", + "sigDigits": 10000, + "name": "Stellitecoin", + "mixIn": 0, + "shortCode": "STL" + }, + "sumo": { + "funcFile": "./lib/coins/sumo.js", + "paymentFile": "./payment_systems/sumo.js", + "sigDigits": 10, + "name": "Sumocoin", + "mixIn": 0, + "shortCode": "SUMO" + }, "aeon": { "funcFile": "./lib/coins/aeon.js", "paymentFile": "./payment_systems/aeon.js", From 04a25427b29086f9d0d509e835770d9e44e73639 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 11:03:09 -0500 Subject: [PATCH 08/22] Create stl.js --- lib/payment_systems/stl.js | 696 +++++++++++++++++++++++++++++++++++++ 1 file changed, 696 insertions(+) create mode 100644 lib/payment_systems/stl.js diff --git a/lib/payment_systems/stl.js b/lib/payment_systems/stl.js new file mode 100644 index 00000000..9b3fb3bb --- /dev/null +++ b/lib/payment_systems/stl.js @@ -0,0 +1,696 @@ +"use strict"; +const shapeshift = require('shapeshift.io'); +const async = require("async"); +const debug = require("debug")("payments"); +const request = require('request-json'); +const range = require('range'); + +let hexChars = new RegExp("[0-9a-f]+"); +let bestExchange = global.config.payout.bestExchange; +let xmrAPIClient = request.createClient('https://xmr.to/api/v1/xmr2btc/'); +let extraPaymentRound = false; +let paymentTimer = null; + +let shapeshiftQueue = async.queue(function (task, callback) { + // Amount needs to be shifted in as a non-completed value, as the wallet will only take non-complete values.. + let amount = task.amount - task.fee; + // Address is the destination address IN BTC. + let address = task.address; + // PaymentIDs are the paymentID's to flag as paid by this transaction. + // Should be a massive list of ID's so we can bulk-update them, by merging them with 's. + // Here we go! General process: Scan shapeshift for valid amounts of funds to xfer around. + // Once there's enough funds, then we active txn + // Do a wallet call to xfer. + // Setup a monitor on the transaction + async.waterfall([ + function (intCallback) { + // Verify if the coin is active in ShapeShift first. + shapeshift.coins(function (err, coinData) { + if (err) { + intCallback(err); + } else if (!coinData.hasOwnProperty(global.config.general.coinCode) || coinData[global.config.general.coinCode].status !== "available") { + intCallback("Coin " + global.config.general.coinCode + " Is not available at this time on shapeshift."); + } else { + intCallback(null); + } + }); + }, + function (intCallback) { + // Get the market information from shapeshift, which includes deposit limits, minimum deposits, rates, etc. + shapeshift.marketInfo(global.config.payout.shapeshiftPair, function (err, marketInfo) { + if (err) { + intCallback(err); + } else if (!marketInfo.hasOwnProperty("limit") || marketInfo.limit <= global.support.coinToDecimal(amount)) { + intCallback("Not enough coin in shapeshift to process at this time."); + } else if (!marketInfo.hasOwnProperty("min") || marketInfo.min >= global.support.coinToDecimal(amount)) { + intCallback("Not enough coin to hit the shapeshift minimum deposits."); + } else { + intCallback(null, marketInfo); + } + }); + }, + function (marketInfo, intCallback) { + // Validated there's enough coin. Time to make our dank txn. + // Return: + /* + { + "orderId": "cc49c556-e645-4c15-a943-d50a935274e4", + "sAddress": "46yzCCD3Mza9tRj7aqPSaxVbbePtuAeKzf8Ky2eRtcXGcEgCg1iTBio6N4sPmznfgGEUGDoBz5CLxZ2XPTyZu1yoCAG7zt6", + "deposit": "d8041668718e6e9d9d0fd335ee5ecd923e6fd074c41316d041cc18b779ade10e", + "depositType": "XMR", + "withdrawal": "1DbxcoCBSA9N7uZvkcvWxuLxSau9q9Pwiu", + "withdrawalType": "BTC", + "public": null, + "apiPubKey": "shapeshift", + "returnAddress": "46XWBqE1iwsVxSDP1qDrxhE1XvsZV6eALG5LwnoMdjbT4GPdy2bZTb99kagzxp2MMjUamTYZ4WgvZdFadvMimTjvR6Gv8hL", + "returnAddressType": "XMR" + } + Valid Statuses: + "received" + "complete" + "error" + "no_deposits" + Complete State Information: + { + "status": "complete", + "address": "d8041668718e6e9d9d0fd335ee5ecd923e6fd074c41316d041cc18b779ade10e", + "withdraw": "1DbxcoCBSA9N7uZvkcvWxuLxSau9q9Pwiu", + "incomingCoin": 3, + "incomingType": "XMR", + "outgoingCoin": "0.04186155", + "outgoingType": "BTC", + "transaction": "be9d97f6fc75262151f8f63e035c6ed638b9eb2a4e93fef43ea63124b045dbfb" + } + */ + shapeshift.shift(address, global.config.payout.shapeshiftPair, {returnAddress: global.config.pool.address}, function (err, returnData) { + if (err) { + intCallback(err); + } else { + global.mysql.query("INSERT INTO shapeshiftTxn (id, address, paymentID, depositType, withdrawl, withdrawlType, returnAddress, returnAddressType, txnStatus) VALUES (?,?,?,?,?,?,?,?,?)", + [returnData.orderId, returnData.sAddress, returnData.deposit, returnData.depositType, returnData.withdrawl, returnData.withdrawlType, returnData.returnAddress, returnData.returnAddressType, 'no_deposits']).then(function () { + intCallback(null, marketInfo, returnData); + }).catch(function (error) { + intCallback(error); + }); + } + }); + }, + function (marketInfo, shapeshiftTxnData, intCallback) { + // Make the payment to ShapeShift + let paymentDetails = { + destinations: [ + { + amount: amount, + address: shapeshiftTxnData.sAddress + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn, + payment_id: shapeshiftTxnData.deposit + }; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + intCallback(null, marketInfo, shapeshiftTxnData, body); + } else { + intCallback("Unknown error from the wallet."); + } + }); + }, + function (marketInfo, shapeshiftTxnData, body, intCallback) { + // body.tx_hash = XMR transaction hash. + // Need to add transaction. + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees, exchange_rate, exchange_name, exchange_txn_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + [1, address, null, task.amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, global.support.decimalToCoin(marketInfo.minerFee), 1, global.support.decimalToCoin(marketInfo.rate), 'shapeshift', shapeshiftTxnData.orderId]).then(function (result) { + intCallback(null, result.insertId); + }).catch(function (error) { + intCallback(error); + }); + } + ], function (err, result) { + if (err) { + console.error("Error processing shapeshift txn: " + JSON.stringify(err)); + callback(true); + } else { + // Need to fill out this data pronto! + console.log("Processed ShapeShift transaction for: " + address + " Paid out: " + result + " payments in the db"); + callback(null, result); + } + }); +}, 2); + +let xmrToQueue = async.queue(function (task, callback) { + // http://xmrto-api.readthedocs.io/en/latest/introduction.html + // Documentation looks good! + // Amount needs to be shifted in as a non-completed value, as the wallet will only take non-complete values.. + let amount = task.amount - task.fee; + // Address is the destination address IN BTC. + let address = task.address; + // PaymentIDs are the paymentID's to flag as paid by this transaction. + // Should be a massive list of ID's so we can bulk-update them, by merging them with 's. + // Here we go! General process: Scan shapeshift for valid amounts of funds to xfer around. + // Once there's enough funds, then we active txn + // Do a wallet call to xfer. + // Setup a monitor on the transaction + async.waterfall([ + function (intCallback) { + // Verify if XMR.to is ready to get to work. + xmrAPIClient.get('order_parameter_query/', function (err, res, body) { + if (err) { + return intCallback(err); + } else if (body.error_msg) { + return intCallback(body.error_msg); + } else { + let amtOfBTC = ((amount / global.config.general.sigDivisor) * body.price).toPrecision(5); + console.log("Attempting to pay: " + address + " Amount: " + amtOfBTC + " BTC or " + amount / global.config.general.sigDivisor + " XMR"); + console.log("Response from XMR.to: " + JSON.stringify(body)); + if (body.lower_limit >= amtOfBTC) { + return intCallback("Not enough XMR to hit the minimum deposit"); + } else if (body.upper_limit <= amtOfBTC) { + return intCallback("Too much XMR to pay out to xmr.to"); + } else { + return intCallback(null, amtOfBTC); + } + } + }); + }, + function (btcValue, intCallback) { + // Validated there's enough coin. Time to make our dank txn. + // Return: + /* + { + "state": "TO_BE_CREATED", + "btc_amount": , + "btc_dest_address": "", + "uuid": "" + } + Valid Statuses: + "TO_BE_CREATED" + "UNPAID" + "UNDERPAID" + "PAID_UNCONFIRMED" + "PAID" + "BTC_SENT" + "TIMED_OUT" + "NOT_FOUND" + // Create, then immediately update with the new information w/ a status call. + */ + console.log("Amount of BTC to pay: " + btcValue); + xmrAPIClient.post('order_create/', { + btc_amount: btcValue, + btc_dest_address: address + }, function (err, res, body) { + if (err) { + return intCallback(err); + } else if (body.error_msg) { + return intCallback(body.error_msg); + } else { + return intCallback(null, body.uuid); + } + }); + }, + function (txnID, intCallback) { + // This function only exists because xmr.to is a pretty little fucking princess. + async.doUntil(function (xmrCallback) { + xmrAPIClient.post('order_status_query/', {uuid: txnID}, function (err, res, body) { + if (err) { + return intCallback(err); + } else if (body.error_msg) { + return intCallback(body.error_msg); + } else { + xmrCallback(null, body.state); + } + }); + }, + function (xmrCallback) { + return xmrCallback !== "TO_BE_CREATED"; + }, + function () { + intCallback(null, txnID); + }); + }, + function (txnID, intCallback) { + xmrAPIClient.post('order_status_query/', {uuid: txnID}, function (err, res, body) { + if (err) { + return intCallback(err); + } else if (body.error_msg) { + return intCallback(body.error_msg); + } else { + console.log(JSON.stringify(body)); + global.mysql.query("INSERT INTO xmrtoTxn (id, address, paymentID, depositType, withdrawl, withdrawlType, returnAddress, returnAddressType, txnStatus, amountDeposited, amountSent) VALUES (?,?,?,?,?,?,?,?,?,?,?)", + [txnID, body.xmr_receiving_address, body.xmr_required_payment_id_long, 'XMR', body.btc_dest_address, 'BTC', global.config.pool.address, 'XMR', body.state_str, global.support.decimalToCoin(body.xmr_amount_total), global.support.decimalToCoin(body.btc_amount)]).then(function () { + return intCallback(null, body, global.support.decimalToCoin(body.xmr_amount_total)); + }).catch(function (error) { + return intCallback(error); + }); + } + }); + }, + function (orderStatus, xmrDeposit, intCallback) { + // Make the payment to ShapeShift + let paymentDetails = { + destinations: [ + { + amount: xmrDeposit, + address: orderStatus.xmr_receiving_address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn, + payment_id: orderStatus.xmr_required_payment_id_long + }; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + return intCallback(null, orderStatus, body); + } else { + return intCallback("Unknown error from the wallet."); + } + }); + }, + function (orderStatus, body, intCallback) { + // body.tx_hash = XMR transaction hash. + // Need to add transaction. + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees, exchange_rate, exchange_name, exchange_txn_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + [1, address, null, global.support.decimalToCoin(orderStatus.xmr_amount_total), body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1, global.support.decimalToCoin(orderStatus.xmr_price_btc), 'xmrto', orderStatus.uuid]).then(function (result) { + return intCallback(null, result.insertId); + }).catch(function (error) { + return intCallback(error); + }); + } + ], function (err, result) { + if (err) { + console.error("Error processing XMRTo txn: " + JSON.stringify(err)); + return callback("Error!"); + } else { + // Need to fill out this data pronto! + console.log("Processed XMRTo transaction for: " + address + " Paid out: " + result + " payments in the db"); + return callback(null, result); + } + }); +}, 2); + +let paymentQueue = async.queue(function (paymentDetails, callback) { + /* + support JSON URI: http://10.0.0.2:28082/json_rpc Args: {"id":"0","jsonrpc":"2.0","method":"transfer","params":{"destinations":[{"amount":68130252045355,"address":"A2MSrn49ziBPJBh8ZNEhhbfyLMou6mao4C1F5TLGUatmUnCxZArDYkcbAnVkVEopWVeak2rKDrmc8JpoS7n5dvfN9YDPBTG"}],"mixin":4,"payment_id":"7e52c5266de9fede7fb3abc0cd88f937b38b51426f7b34ff99729d28ce4e1142"}} +1ms + payments Payment made: {"id":"0","jsonrpc":"2.0","result":{"fee":40199391255,"tx_hash":"c418708643f72635edf522490bfb2cae9d42a6dc1df30dcde844862dfd88f5b3","tx_key":""}} +2s + */ + if (paymentTimer !== null){ + clearInterval(paymentTimer); + paymentTimer = null; + } + debug("Making payment based on: " + JSON.stringify(paymentDetails)); + let transferFunc = 'transfer'; + global.support.rpcWallet(transferFunc, paymentDetails, function (body) { + debug("Payment made: " + JSON.stringify(body)); + if (body.hasOwnProperty('error')) { + if (body.error.message === "not enough money"){ + console.error("Issue making payments, not enough money, will try later"); + if(!extraPaymentRound){ + setTimeout(function(){ + makePayments(); + }, global.config.payout.timerRetry * 60 * 1000); + } + extraPaymentRound = true; + return callback(false); + } else { + console.error("Issue making payments" + JSON.stringify(body.error)); + console.error("Will not make more payments until the payment daemon is restarted!"); + //toAddress, subject, body + global.support.sendEmail(global.config.general.adminEmail, "Payment daemon unable to make payment", + "Hello,\r\nThe payment daemon has hit an issue making a payment: " + JSON.stringify(body.error) + + ". Please investigate and restart the payment daemon as appropriate"); + return; + } + } + if (paymentDetails.hasOwnProperty('payment_id')) { + console.log("Payment made to " + paymentDetails.destinations[0].address + " with PaymentID: " + paymentDetails.payment_id + " For: " + global.support.coinToDecimal(paymentDetails.destinations[0].amount) + " XMR with a " + global.support.coinToDecimal(body.result.fee) + " XMR Mining Fee"); + return callback(body.result); + } else { + if (transferFunc === 'transfer') { + console.log("Payment made out to multiple people, total fee: " + global.support.coinToDecimal(body.result.fee) + " XMR"); + } + let intCount = 0; + paymentDetails.destinations.forEach(function (details) { + console.log("Payment made to: " + details.address + " For: " + global.support.coinToDecimal(details.amount) + " XMR"); + intCount += 1; + if (intCount === paymentDetails.destinations.length) { + return callback(body.result); + } + }); + } + }); +}, 1); + +paymentQueue.drain = function(){ + extraPaymentRound = false; + if (global.config.payout.timer > 35791){ + console.error("Payout timer is too high. Please use a value under 35791 to avoid overflows."); + } else { + paymentTimer = setInterval(makePayments, global.config.payout.timer * 60 * 1000); + } + global.database.setCache('lastPaymentCycle', Math.floor(Date.now()/1000)); +}; + +function updateShapeshiftCompletion() { + global.mysql.query("SELECT * FROM shapeshiftTxn WHERE txnStatus NOT IN ('complete', 'error')").then(function (rows) { + rows.forEach(function (row) { + shapeshift.status(row.paymentID, function (err, status, returnData) { + if (err) { + return; + } + global.mysql.query("UPDATE shapeshiftTxn SET txnStatus = ? WHERE id = ?", [status, row.id]).then(function () { + if (status === 'complete') { + global.mysql.query("UPDATE shapeshiftTxn SET amountDeposited = ?, amountSent = ?, transactionHash = ? WHERE id = ?", + [global.support.decimalToCoin(returnData.incomingCoin), global.support.bitcoinDecimalToCoin(returnData.outgoingCoin), returnData.transaction, row.id]).then(function () { + global.mysql.query("UPDATE transactions SET confirmed = 1, confirmed_time = now(), btc_amt = ? WHERE exchange_txn_id = ?", [global.support.bitcoinDecimalToCoin(returnData.outgoingCoin), row.id]); + }); + } else if (status === 'error') { + // Failed txn. Need to rollback and delete all related data. Here we go! + global.mysql.query("DELETE FROM shapeshiftTxn WHERE id = ?", [row.id]); + global.mysql.query("SELECT id, xmr_amt, address FROM transactions WHERE exchange_txn_id = ?", [row.id]).then(function (rows) { + global.mysql.query("DELETE FROM transactions WHERE id = ?", [rows[0].id]); + global.mysql.query("DELETE payments WHERE transaction_id = ?", [rows[0].id]); + global.mysql.query("UPDATE balance SET amount = amount+? WHERE payment_address = ? limit 1", [rows[0].xmr_amt, rows[0].address]); + }); + console.error("Failed transaction from ShapeShift " + JSON.stringify(returnData)); + } + }); + }); + }); + }); +} + +function updateXMRToCompletion() { + global.mysql.query("SELECT * FROM xmrtoTxn WHERE txnStatus NOT IN ('PAID', 'TIMED_OUT', 'NOT_FOUND', 'BTC_SENT')").then(function (rows) { + rows.forEach(function (row) { + xmrAPIClient.post('order_status_query/', {uuid: row.id}, function (err, res, body) { + if (err) { + console.log("Error in getting order status: " + JSON.stringify(err)); + return; + } + if (body.error_msg) { + console.log("Error in getting order status: " + body.error_msg); + return; + } + global.mysql.query("UPDATE xmrtoTxn SET txnStatus = ? WHERE id = ?", [body.state, row.id]).then(function () { + if (body.status === 'BTC_SENT') { + global.mysql.query("UPDATE xmrtoTxn SET transactionHash = ? WHERE id = ?", [body.btc_transaction_id, row.id]).then(function () { + global.mysql.query("UPDATE transactions SET confirmed = 1, confirmed_time = now(), btc_amt = ? WHERE exchange_txn_id = ?", [global.support.bitcoinDecimalToCoin(body.btc_amount), row.id]); + }); + } else if (body.status === 'TIMED_OUT' || body.status === 'NOT_FOUND') { + global.mysql.query("DELETE FROM xmrtoTxn WHERE id = ?", [row.id]); + global.mysql.query("SELECT id, xmr_amt, address FROM transactions WHERE exchange_txn_id = ?", [row.id]).then(function (rows) { + global.mysql.query("DELETE FROM transactions WHERE id = ?", [rows[0].id]); + global.mysql.query("DELETE payments WHERE transaction_id = ?", [rows[0].id]); + global.mysql.query("UPDATE balance SET amount = amount+? WHERE payment_address = ? limit 1", [rows[0].xmr_amt, rows[0].address]); + }); + console.error("Failed transaction from XMRto " + JSON.stringify(body)); + } + }); + }); + }); + }); +} + +function determineBestExchange() { + async.waterfall([ + function (callback) { + // Verify if the coin is active in ShapeShift first. + shapeshift.coins(function (err, coinData) { + if (err) { + return callback(err); + } else if (!coinData.hasOwnProperty(global.config.general.coinCode) || coinData[global.config.general.coinCode].status !== "available") { + return callback("Coin " + global.config.general.coinCode + " Is not available at this time on shapeshift."); + } else { + return callback(null); + } + }); + }, + function (callback) { + // Get the market information from shapeshift, which includes deposit limits, minimum deposits, rates, etc. + shapeshift.marketInfo(global.config.payout.shapeshiftPair, function (err, marketInfo) { + if (err) { + return callback(err); + } else if (!marketInfo.hasOwnProperty("rate")) { + return callback("Shapeshift did not return the rate."); + } else { + return callback(null, global.support.bitcoinDecimalToCoin(marketInfo.rate)); + } + }); + }, + function (ssValue, callback) { + xmrAPIClient.get('order_parameter_query/', function (err, res, body) { + console.log("XMR.to pricing body: " + JSON.stringify(body)); + if (err) { + return callback(err); + } else if (body.error_msg) { + return callback(body.error_msg); + } else { + return callback(null, ssValue, global.support.bitcoinDecimalToCoin(body.price)); + } + }); + } + ], function (err, ssValue, xmrToValue) { + if (err) { + return console.error("Error processing exchange value: " + JSON.stringify(err)); + } + debug("ShapeShift Value: " + global.support.bitcoinCoinToDecimal(ssValue) + " XMR.to Value: " + global.support.bitcoinCoinToDecimal(xmrToValue)); + if (ssValue >= xmrToValue) { + console.log("ShapeShift is the better BTC exchange, current rate: " + global.support.bitcoinCoinToDecimal(ssValue)); + bestExchange = 'shapeshift'; + global.mysql.query("UPDATE config SET item_value = 'shapeshift' where item='bestExchange'"); + global.mysql.query("UPDATE config SET item_value = ? where item='exchangeRate'", [ssValue]); + } else { + console.log("XMR.to is the better BTC exchange, current rate: " + global.support.bitcoinCoinToDecimal(xmrToValue)); + bestExchange = 'xmrto'; + global.mysql.query("UPDATE config SET item_value = 'xmrto' where item='bestExchange'"); + global.mysql.query("UPDATE config SET item_value = ? where item='exchangeRate'", [xmrToValue]); + } + }); +} + +function Payee(amount, address, paymentID, bitcoin) { + this.amount = amount; + this.address = address; + this.paymentID = paymentID; + this.bitcoin = bitcoin; + this.blockID = 0; + this.poolType = ''; + this.transactionID = 0; + this.sqlID = 0; + if (paymentID === null) { + this.id = address; + } else { + this.id = address + "." + paymentID; + } + this.fee = 0; + this.baseFee = global.support.decimalToCoin(global.config.payout.feeSlewAmount); + this.setFeeAmount = function () { + if (this.amount <= global.support.decimalToCoin(global.config.payout.walletMin)) { + this.fee = this.baseFee; + } else if (this.amount <= global.support.decimalToCoin(global.config.payout.feeSlewEnd)) { + let feeValue = this.baseFee / (global.support.decimalToCoin(global.config.payout.feeSlewEnd) - global.support.decimalToCoin(global.config.payout.walletMin)); + this.fee = this.baseFee - ((this.amount - global.support.decimalToCoin(global.config.payout.walletMin)) * feeValue); + } + this.fee = Math.floor(this.fee); + }; + + this.makePaymentWithID = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn, + payment_id: this.paymentID + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let paymentID = this.paymentID; + let payee = this; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, address, paymentID, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.makePaymentAsIntegrated = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let payee = this; + + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?)", + [0, address, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.makeBitcoinPayment = function () { + let functionalData = {address: this.address, amount: this.amount, fee: this.fee}; + let payee = this; + if (bestExchange === 'xmrto') { + xmrToQueue.push(functionalData, function (err, transactionID) { + if (err) { + return console.error("Error processing payment for " + functionalData.address); + } + payee.transactionID = transactionID; + payee.trackPayment(); + }); + } else { + shapeshiftQueue.push(functionalData, function (err, transactionID) { + if (err) { + return console.error("Error processing payment for " + functionalData.address); + } + payee.transactionID = transactionID; + payee.trackPayment(); + }); + } + }; + + this.trackPayment = function () { + global.mysql.query("UPDATE balance SET amount = amount - ? WHERE id = ?", [this.amount, this.sqlID]); + global.mysql.query("INSERT INTO payments (unlocked_time, paid_time, pool_type, payment_address, transaction_id, bitcoin, amount, payment_id, transfer_fee)" + + " VALUES (now(), now(), ?, ?, ?, ?, ?, ?, ?)", [this.poolType, this.address, this.transactionID, this.bitcoin, this.amount - this.fee, this.paymentID, this.fee]); + }; +} + +function makePayments() { + global.mysql.query("SELECT * FROM balance WHERE amount >= ?", [global.support.decimalToCoin(global.config.payout.walletMin)]).then(function (rows) { + console.log("Loaded all payees into the system for processing"); + let paymentDestinations = []; + let totalAmount = 0; + let roundCount = 0; + let payeeList = []; + let payeeObjects = {}; + rows.forEach(function (row) { + debug("Starting round for: " + JSON.stringify(row)); + let payee = new Payee(row.amount, row.payment_address, row.payment_id, row.bitcoin); + payeeObjects[row.payment_address] = payee; + global.mysql.query("SELECT payout_threshold FROM users WHERE username = ?", [payee.id]).then(function (userRow) { + roundCount += 1; + let threshold = 0; + if (userRow.length !== 0) { + threshold = userRow[0].payout_threshold; + } + payee.poolType = row.pool_type; + payee.sqlID = row.id; + if (payee.poolType === "fees" && payee.address === global.config.payout.feeAddress && payee.amount >= ((global.support.decimalToCoin(global.config.payout.feesForTXN) + global.support.decimalToCoin(global.config.payout.exchangeMin)))) { + debug("This is the fee address internal check for value"); + payee.amount -= global.support.decimalToCoin(global.config.payout.feesForTXN); + } else if (payee.address === global.config.payout.feeAddress && payee.poolType === "fees") { + debug("Unable to pay fee address."); + payee.amount = 0; + } + let remainder = payee.amount % (global.config.payout.denom * global.config.general.sigDivisor); + if (remainder !== 0) { + payee.amount -= remainder; + } + if (payee.amount > threshold) { + payee.setFeeAmount(); + if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length !== 106) { + debug("Adding " + payee.id + " to the list of people to pay (OG Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + paymentDestinations.push({amount: payee.amount - payee.fee, address: payee.address}); + totalAmount += payee.amount; + payeeList.push(payee); + } else if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length === 106 && (payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0))) { + // Special code to handle integrated payment addresses. What a pain in the rear. + // These are exchange addresses though, so they need to hit the exchange payout amount. + debug("Adding " + payee.id + " to the list of people to pay (Integrated Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makePaymentAsIntegrated(); + } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 0) { + debug("Adding " + payee.id + " to the list of people to pay (Payment ID Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makePaymentWithID(); + } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 1) { + debug("Adding " + payee.id + " to the list of people to pay (Bitcoin Payout). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makeBitcoinPayment(); + } + } + debug("Went: " + roundCount + " With: " + paymentDestinations.length + " Possible destinations and: " + rows.length + " Rows"); + if (roundCount === rows.length && paymentDestinations.length > 0) { + while (paymentDestinations.length > 0) { + let paymentDetails = { + destinations: paymentDestinations.splice(0, global.config.payout.maxPaymentTxns), + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn + }; + console.log("Paying out: " + paymentDetails.destinations.length + " people"); + paymentQueue.push(paymentDetails, function (body) { //jshint ignore:line + // This is the only section that could potentially contain multiple txns. Lets do this safely eh? + if (body.fee && body.fee > 10) { + debug("Made it to the SQL insert for transactions"); + let totalAmount = 0; + paymentDetails.destinations.forEach(function (payeeItem) { + totalAmount += payeeObjects[payeeItem.address].amount; + totalAmount += payeeObjects[payeeItem.address].fee; + }); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, null, null, totalAmount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, paymentDetails.destinations.length]).then(function (result) { + paymentDetails.destinations.forEach(function (payeeItem) { + payee = payeeObjects[payeeItem.address]; + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + } + } + }); + }); + }); +} + +function init() { + global.support.rpcWallet("store", [], function () { + }); + if (global.config.allowBitcoin) { + determineBestExchange(); + setInterval(updateXMRToCompletion, 90000); + setInterval(updateShapeshiftCompletion, 90000); + setInterval(determineBestExchange, 60000); + } + setInterval(function () { + global.support.rpcWallet("store", [], function () { + }); + }, 60000); + console.log("Setting the payment timer to: " + global.config.payout.timer + " minutes with a: " + global.config.payout.timerRetry + " minute delay if the wallet is out of money"); + makePayments(); +} + +init(); From d65633b82f894393cc3753f22d1821b86c7a78a8 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 11:03:38 -0500 Subject: [PATCH 09/22] Create stl.js --- lib/coins/stl.js | 162 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 lib/coins/stl.js diff --git a/lib/coins/stl.js b/lib/coins/stl.js new file mode 100644 index 00000000..0618c78b --- /dev/null +++ b/lib/coins/stl.js @@ -0,0 +1,162 @@ +"use strict"; +const bignum = require('bignum'); +const cnUtil = require('cryptonote-util'); +const multiHashing = require('multi-hashing'); +const crypto = require('crypto'); +const debug = require('debug')('coinFuncs'); + +let hexChars = new RegExp("[0-9a-f]+"); + +function Coin(data){ + this.bestExchange = global.config.payout.bestExchange; + this.data = data; + let instanceId = crypto.randomBytes(4); + this.coinDevAddress = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; // Developer Address + this.poolDevAddress = "44Ldv5GQQhP7K7t3ZBdZjkPA7Kg7dhHwk3ZM3RJqxxrecENSFx27Vq14NAMAd2HBvwEPUVVvydPRLcC69JCZDHLT2X5a4gr"; // Snipa Address + + this.blockedAddresses = [ + + ]; + + this.exchangeAddresses = [ + + ]; // These are addresses that MUST have a paymentID to perform logins with. + + //this.prefix = cnUtil.address_decode(new Buffer(config.poolServer.poolAddress)) + this.prefix = 9241; + this.intPrefix = 28822; //cnUtil.address_decode_integrated(new Buffer(config.poolServer.poolAddress)); + + if (global.config.general.testnet === true){ + this.prefix = 53; + this.intPrefix = 54; + } + + this.supportsAutoExchange = true; + + this.niceHashDiff = 400000; + + this.getBlockHeaderByID = function(blockId, callback){ + global.support.rpcDaemon('getblockheaderbyheight', {"height": blockId}, function (body) { + if (body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockHeaderByHash = function(blockHash, callback){ + global.support.rpcDaemon('getblockheaderbyhash', {"hash": blockHash}, function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getLastBlockHeader = function(callback){ + global.support.rpcDaemon('getlastblockheader', [], function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockTemplate = function(walletAddress, callback){ + global.support.rpcDaemon('getblocktemplate', { + reserve_size: 17, + wallet_address: walletAddress + }, function(body){ + return callback(body); + }); + }; + + this.baseDiff = function(){ + return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); + }; + + this.validateAddress = function(address){ + // This function should be able to be called from the async library, as we need to BLOCK ever so slightly to verify the address. + address = new Buffer(address); + if (cnUtil.address_decode(address) === this.prefix){ + return true; + } + return cnUtil.address_decode_integrated(address) === this.intPrefix; + }; + + this.convertBlob = function(blobBuffer){ + return cnUtil.convert_blob(blobBuffer); + }; + + this.constructNewBlob = function(blockTemplate, NonceBuffer){ + return cnUtil.construct_block_blob(blockTemplate, NonceBuffer); + }; + + this.getBlockID = function(blockBuffer){ + return cnUtil.get_block_id(blockBuffer); + }; + + this.BlockTemplate = function(template) { + /* + Generating a block template is a simple thing. Ask for a boatload of information, and go from there. + Important things to consider. + The reserved space is 13 bytes long now in the following format: + Assuming that the extraNonce starts at byte 130: + |130-133|134-137|138-141|142-145| + |minerNonce/extraNonce - 4 bytes|instanceId - 4 bytes|clientPoolNonce - 4 bytes|clientNonce - 4 bytes| + This is designed to allow a single block template to be used on up to 4 billion poolSlaves (clientPoolNonce) + Each with 4 billion clients. (clientNonce) + While being unique to this particular pool thread (instanceId) + With up to 4 billion clients (minerNonce/extraNonce) + Overkill? Sure. But that's what we do here. Overkill. + */ + + // Set this.blob equal to the BT blob that we get from upstream. + this.blob = template.blocktemplate_blob; + this.idHash = crypto.createHash('md5').update(template.blocktemplate_blob).digest('hex'); + // Set this.diff equal to the known diff for this block. + this.difficulty = template.difficulty; + // Set this.height equal to the known height for this block. + this.height = template.height; + // Set this.reserveOffset to the byte location of the reserved offset. + this.reserveOffset = template.reserved_offset; + // Set this.buffer to the binary decoded version of the BT blob. + this.buffer = new Buffer(this.blob, 'hex'); + // Copy the Instance ID to the reserve offset + 4 bytes deeper. Copy in 4 bytes. + instanceId.copy(this.buffer, this.reserveOffset + 4, 0, 3); + // Generate a clean, shiny new buffer. + this.previous_hash = new Buffer(32); + // Copy in bytes 7 through 39 to this.previous_hash from the current BT. + this.buffer.copy(this.previous_hash, 0, 7, 39); + // Reset the Nonce. - This is the per-miner/pool nonce + this.extraNonce = 0; + // The clientNonceLocation is the location at which the client pools should set the nonces for each of their clients. + this.clientNonceLocation = this.reserveOffset + 12; + // The clientPoolLocation is for multi-thread/multi-server pools to handle the nonce for each of their tiers. + this.clientPoolLocation = this.reserveOffset + 8; + this.nextBlob = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Convert the blob into something hashable. + return global.coinFuncs.convertBlob(this.buffer).toString('hex'); + }; + // Make it so you can get the raw block blob out. + this.nextBlobWithChildNonce = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Don't convert the blob to something hashable. You bad. + return this.buffer.toString('hex'); + }; + }; + + this.cryptoNight = multiHashing.cryptonight; + +} + +module.exports = Coin; From cc00d5a0754f1e11ecdb9e6c9ec8cc41d03a3739 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 11:15:43 -0500 Subject: [PATCH 10/22] Create itns.js --- lib/payment_systems/itns.js | 302 ++++++++++++++++++++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 lib/payment_systems/itns.js diff --git a/lib/payment_systems/itns.js b/lib/payment_systems/itns.js new file mode 100644 index 00000000..6fbe2125 --- /dev/null +++ b/lib/payment_systems/itns.js @@ -0,0 +1,302 @@ +"use strict"; +const async = require("async"); +const debug = require("debug")("payments"); + +let hexChars = new RegExp("[0-9a-f]+"); +let extraPaymentRound = false; +let paymentTimer = null; + +let paymentQueue = async.queue(function (paymentDetails, callback) { + if (paymentTimer !== null) { + clearInterval(paymentTimer); + paymentTimer = null; + } + + // Protect our balance by making sure we have sufficient unlocked funds before trying to transact + global.support.rpcWallet('getbalance', [], function (body) { + if (body.result) { + var amountToPay = paymentDetails.destinations.reduce(function (a, b) { return a + b; }, 0); + if (body.result.unlocked_balance < amountToPay) { + console.error("Wallet only has " + body.result.unlocked_balance + " unlocked balance, can't pay " + amountToPay + " worth of ITNS. Retrying in " + timerRetry + " minutes!"); + if (!extraPaymentRound) { + setTimeout(function () { + makePayments(); + }, global.config.payout.timerRetry * 60 * 1000); + } + extraPaymentRound = true; + return callback(false); + } + } else { + console.error("Issue checking pool wallet balance before making payments" + JSON.stringify(body.error)); + console.error("Will not make more payments until the payment daemon is restarted!"); + //toAddress, subject, body + global.support.sendEmail(global.config.general.adminEmail, "Payment daemon unable to check wallet balance", + "Hello,\r\nThe payment daemon has hit an issue checking the pool's wallet balance: " + JSON.stringify(body.error) + + ". Please investigate and restart the payment daemon as appropriate"); + return; + } + }); + + debug("Making payment based on: " + JSON.stringify(paymentDetails)); + let transferFunc = 'transfer'; + global.support.rpcWallet(transferFunc, paymentDetails, function (body) { + debug("Payment made: " + JSON.stringify(body)); + if (body.hasOwnProperty('error')) { + if (body.error.message === "not enough money") { + // In theory the earlier check should prevent this from occurring. + console.error("Issue making payments, not enough money, will try later"); + if (!extraPaymentRound) { + setTimeout(function () { + makePayments(); + }, global.config.payout.timerRetry * 60 * 1000); + } + extraPaymentRound = true; + return callback(false); + } else { + console.error("Issue making payments" + JSON.stringify(body.error)); + console.error("Will not make more payments until the payment daemon is restarted!"); + //toAddress, subject, body + global.support.sendEmail(global.config.general.adminEmail, "Payment daemon unable to make payment", + "Hello,\r\nThe payment daemon has hit an issue making a payment: " + JSON.stringify(body.error) + + ". Please investigate and restart the payment daemon as appropriate"); + return; + } + } + + if (paymentDetails.hasOwnProperty('payment_id')) { + console.log("Payment made to " + paymentDetails.destinations[0].address + " with PaymentID: " + paymentDetails.payment_id + " For: " + global.support.coinToDecimal(paymentDetails.destinations[0].amount) + " ITNS with a " + global.support.coinToDecimal(body.result.fee) + " ITNS Mining Fee"); + return callback(body.result); + } else { + if (transferFunc === 'transfer') { + console.log("Payment made out to multiple people, total fee: " + global.support.coinToDecimal(body.result.fee) + " ITNS"); + } + let intCount = 0; + paymentDetails.destinations.forEach(function (details) { + console.log("Payment made to: " + details.address + " For: " + global.support.coinToDecimal(details.amount) + " ITNS"); + intCount += 1; + if (intCount === paymentDetails.destinations.length) { + return callback(body.result); + } + }); + } + }); +}, 1); + +paymentQueue.drain = function () { + extraPaymentRound = false; + startNormalPaymentTimer(); + global.database.setCache('lastPaymentCycle', Math.floor(Date.now() / 1000)); +}; + +function Payee(amount, address, paymentID, bitcoin) { + this.amount = amount; + this.address = address; + this.paymentID = paymentID; + this.bitcoin = bitcoin; + this.blockID = 0; + this.poolType = ''; + this.transactionID = 0; + this.sqlID = 0; + if (paymentID === null) { + this.id = address; + } else { + this.id = address + "." + paymentID; + } + this.fee = 0; + this.baseFee = global.support.decimalToCoin(global.config.payout.feeSlewAmount); + this.setFeeAmount = function () { + if (this.amount <= global.support.decimalToCoin(global.config.payout.walletMin)) { + this.fee = this.baseFee; + } else if (this.amount <= global.support.decimalToCoin(global.config.payout.feeSlewEnd)) { + let feeValue = this.baseFee / (global.support.decimalToCoin(global.config.payout.feeSlewEnd) - global.support.decimalToCoin(global.config.payout.walletMin)); + this.fee = this.baseFee - ((this.amount - global.support.decimalToCoin(global.config.payout.walletMin)) * feeValue); + } + this.fee = Math.floor(this.fee); + }; + + this.makePaymentWithID = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn, + payment_id: this.paymentID + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let paymentID = this.paymentID; + let payee = this; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, address, paymentID, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + if (global.config.general.discordPayoutAnnounce === true) { + global.support.poolBotMsg("Pool paid out: " + global.support.coinToDecimal(amount) + " ITNS to 1 miner"); + } + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.makePaymentAsIntegrated = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let payee = this; + + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?)", + [0, address, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + if (global.config.general.discordPayoutAnnounce === true) { + global.support.poolBotMsg("Pool paid out: " + global.support.coinToDecimal(amount) + " ITNS to 1 miner"); + } + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.trackPayment = function () { + global.mysql.query("UPDATE balance SET amount = amount - ? WHERE id = ?", [this.amount, this.sqlID]); + global.mysql.query("INSERT INTO payments (unlocked_time, paid_time, pool_type, payment_address, transaction_id, bitcoin, amount, payment_id, transfer_fee)" + + " VALUES (now(), now(), ?, ?, ?, ?, ?, ?, ?)", [this.poolType, this.address, this.transactionID, this.bitcoin, this.amount - this.fee, this.paymentID, this.fee]); + }; +} + +function makePayments() { + global.mysql.query("SELECT * FROM balance WHERE amount >= ?", [global.support.decimalToCoin(global.config.payout.walletMin)]).then(function (rows) { + console.log("Loaded all payees into the system for processing"); + let paymentDestinations = []; + let totalAmount = 0; + let roundCount = 0; + let payeeList = []; + let payeeObjects = {}; + rows.forEach(function (row) { + debug("Starting round for: " + JSON.stringify(row)); + let payee = new Payee(row.amount, row.payment_address, row.payment_id, row.bitcoin); + payeeObjects[row.payment_address] = payee; + global.mysql.query("SELECT payout_threshold FROM users WHERE username = ?", [payee.id]).then(function (userRow) { + roundCount += 1; + let threshold = 0; + if (userRow.length !== 0) { + threshold = userRow[0].payout_threshold; + } + payee.poolType = row.pool_type; + payee.sqlID = row.id; + if (payee.poolType === "fees" && payee.address === global.config.payout.feeAddress && payee.amount >= ((global.support.decimalToCoin(global.config.payout.feesForTXN) + global.support.decimalToCoin(global.config.payout.exchangeMin)))) { + debug("This is the fee address internal check for value"); + payee.amount -= global.support.decimalToCoin(global.config.payout.feesForTXN); + } else if (payee.address === global.config.payout.feeAddress && payee.poolType === "fees") { + debug("Unable to pay fee address."); + payee.amount = 0; + } + let remainder = payee.amount % (global.config.payout.denom * global.config.general.sigDivisor); + if (remainder !== 0) { + payee.amount -= remainder; + } + if (payee.amount > threshold) { + payee.setFeeAmount(); + if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && global.coinFuncs.isIntegratedAddress(payee.address) === false) { + debug("Adding " + payee.id + " to the list of people to pay (OG Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + paymentDestinations.push({ amount: payee.amount - payee.fee, address: payee.address }); + totalAmount += payee.amount; + payeeList.push(payee); + } else if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && global.coinFuncs.isIntegratedAddress(payee.address) === true && (payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0))) { + // Special code to handle integrated payment addresses. What a pain in the rear. + // These are exchange addresses though, so they need to hit the exchange payout amount. + debug("Adding " + payee.id + " to the list of people to pay (Integrated Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makePaymentAsIntegrated(); + } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 0) { + debug("Adding " + payee.id + " to the list of people to pay (Payment ID Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makePaymentWithID(); + } + } + debug("Went: " + roundCount + " With: " + paymentDestinations.length + " Possible destinations and: " + rows.length + " Rows"); + if (roundCount === rows.length && paymentDestinations.length > 0) { + while (paymentDestinations.length > 0) { + let paymentDetails = { + destinations: paymentDestinations.splice(0, global.config.payout.maxPaymentTxns), + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn + }; + console.log("Paying out: " + paymentDetails.destinations.length + " people"); + paymentQueue.push(paymentDetails, function (body) { //jshint ignore:line + // This is the only section that could potentially contain multiple txns. Lets do this safely eh? + if (body.fee && body.fee > 10) { + debug("Made it to the SQL insert for transactions"); + let totalAmount = 0; + paymentDetails.destinations.forEach(function (payeeItem) { + totalAmount += payeeObjects[payeeItem.address].amount; + totalAmount += payeeObjects[payeeItem.address].fee; + }); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, null, null, totalAmount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, paymentDetails.destinations.length]).then(function (result) { + paymentDetails.destinations.forEach(function (payeeItem) { + payee = payeeObjects[payeeItem.address]; + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + }); + if (global.config.general.discordPayoutAnnounce === true) { + global.support.poolBotMsg("Pool paid out: " + global.support.coinToDecimal(totalAmount) + " ITNS to " + paymentDetails.destinations.length + " miners"); + } + } else { + console.error("Unknown error from the wallet."); + } + }); + } + } + }); + }); + }); +} + +function startNormalPaymentTimer() { + if (global.config.payout.timer > 35791) { + console.error("Payout timer is too high. Please use a value under 35791 to avoid overflows."); + } else { + console.log("Setting the payment timer to: " + global.config.payout.timer + " minutes for its next normal run."); + paymentTimer = setInterval(makePayments, global.config.payout.timer * 60 * 1000); + } +} + +function init() { + global.support.rpcWallet("store", [], function () { + }); + setInterval(function () { + global.support.rpcWallet("store", [], function () { + }); + }, 60000); + console.log("(Payout Timer Configuration) Normal Timer: " + global.config.payout.timer + " minutes, Out of Money Retry Timer: " + global.config.payout.timerRetry + " minutes"); + startNormalPaymentTimer(); + makePayments(); +} + +init(); From 94ad2ec43fa0a4caff0f66cbe2feb2ae8a9174c1 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 11:16:23 -0500 Subject: [PATCH 11/22] Create itns.js --- lib/coins/itns.js | 200 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 lib/coins/itns.js diff --git a/lib/coins/itns.js b/lib/coins/itns.js new file mode 100644 index 00000000..21a559ea --- /dev/null +++ b/lib/coins/itns.js @@ -0,0 +1,200 @@ +"use strict"; +const bignum = require('bignum'); +const cnUtil = require('intensecoin-util'); +const multiHashing = require('multi-hashing'); +const crypto = require('crypto'); +const debug = require('debug')('coinFuncs'); + +let hexChars = new RegExp("[0-9a-f]+"); + +function Coin(data){ + this.bestExchange = global.config.payout.bestExchange; + this.data = data; + let instanceId = crypto.randomBytes(4); + this.coinDevAddress = "iz5w5LGYQY2SseEd9BTaF8SRqFmZLTEVDBEGidvzYnZBcc9RMEHXs2rXBZfAvXQPPc85NR2JeZcQUj7jjBcgw26b1Rk6m4H2z"; // Developer Address + this.poolDevAddress = "iz5imhe9C7vWnjZtZBFtT8MwNxVuJuryUUHXSAtnWUo93CJzNdZBizHQExPRCHUBi36tk2BcigPAFRDA4cnddGXF1R6j69n3w"; // Venthos Address + + this.blockedAddresses = [ + this.coinDevAddress, + this.poolDevAddress + ]; + + this.exchangeAddresses = [ + "iz4pcDLxmo7KqbFmYjE5aGDv68U9Sgm1ePFjWUY24vzyPeGMcoG894MAFjrtHbaMv1TygTcvJWzGN3zNR6PeEYuc1w8V2tiMW" // stocks.exchange + ]; // These are addresses that MUST have a paymentID to perform logins with. + + this.prefix = 251; + this.intPrefix = 129; + + if (global.config.general.testnet === true) { + this.prefix = 25247; + this.intPrefix = 3745; + } + + this.supportsAutoExchange = false; + + this.niceHashDiff = 400000; + + this.getBlockHeaderByHash = function(blockHash, callback){ + global.support.rpcDaemon('getblockheaderbyhash', {"hash": blockHash}, function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockHeaderByHeight = function(blockHeight, callback){ + global.support.rpcDaemon('getblockheaderbyheight', {"height": blockHeight}, function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getLastBlockHeader = function(callback){ + global.support.rpcDaemon('getlastblockheader', [], function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockTemplate = function(walletAddress, callback){ + global.support.rpcDaemon('getblocktemplate', { + reserve_size: 17, + wallet_address: walletAddress + }, function(body){ + return callback(body); + }); + }; + + this.submitBlock = function(blockBlobData, callback){ + global.support.rpcDaemon('submitblock', [blockBlobData], function(body){ + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.status); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBalance = function(callback){ + global.support.rpcWallet('getbalance', [], function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getHeight = function(callback){ + global.support.rpcWallet('getheight', [], function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.height); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.baseDiff = function(){ + return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); + }; + + this.validateAddress = function(address){ + // This function should be able to be called from the async library, as we need to BLOCK ever so slightly to verify the address. + address = new Buffer(address); + if (cnUtil.address_decode(address) === this.prefix){ + return true; + } + return cnUtil.address_decode_integrated(address) === this.intPrefix; + }; + + this.isIntegratedAddress = function(address) { + address = new Buffer(address); + return cnUtil.address_decode_integrated(address) === this.intPrefix; + }; + + this.convertBlob = function(blobBuffer){ + return cnUtil.convert_blob(blobBuffer); + }; + + this.constructNewBlob = function(blockTemplate, NonceBuffer){ + return cnUtil.construct_block_blob(blockTemplate, NonceBuffer); + }; + + this.getBlockID = function(blockBuffer){ + return cnUtil.get_block_id(blockBuffer); + }; + + this.BlockTemplate = function(template) { + /* + Generating a block template is a simple thing. Ask for a boatload of information, and go from there. + Important things to consider. + The reserved space is 13 bytes long now in the following format: + Assuming that the extraNonce starts at byte 130: + |130-133|134-137|138-141|142-145| + |minerNonce/extraNonce - 4 bytes|instanceId - 4 bytes|clientPoolNonce - 4 bytes|clientNonce - 4 bytes| + This is designed to allow a single block template to be used on up to 4 billion poolSlaves (clientPoolNonce) + Each with 4 billion clients. (clientNonce) + While being unique to this particular pool thread (instanceId) + With up to 4 billion clients (minerNonce/extraNonce) + Overkill? Sure. But that's what we do here. Overkill. + */ + + // Set this.blob equal to the BT blob that we get from upstream. + this.blob = template.blocktemplate_blob; + this.idHash = crypto.createHash('md5').update(template.blocktemplate_blob).digest('hex'); + // Set this.diff equal to the known diff for this block. + this.difficulty = template.difficulty; + // Set this.height equal to the known height for this block. + this.height = template.height; + // Set this.reserveOffset to the byte location of the reserved offset. + this.reserveOffset = template.reserved_offset; + // Set this.buffer to the binary decoded version of the BT blob. + this.buffer = new Buffer(this.blob, 'hex'); + // Copy the Instance ID to the reserve offset + 4 bytes deeper. Copy in 4 bytes. + instanceId.copy(this.buffer, this.reserveOffset + 4, 0, 3); + // Generate a clean, shiny new buffer. + this.previous_hash = new Buffer(32); + // Copy in bytes 7 through 39 to this.previous_hash from the current BT. + this.buffer.copy(this.previous_hash, 0, 7, 39); + // Reset the Nonce. - This is the per-miner/pool nonce + this.extraNonce = 0; + // The clientNonceLocation is the location at which the client pools should set the nonces for each of their clients. + this.clientNonceLocation = this.reserveOffset + 12; + // The clientPoolLocation is for multi-thread/multi-server pools to handle the nonce for each of their tiers. + this.clientPoolLocation = this.reserveOffset + 8; + this.nextBlob = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Convert the blob into something hashable. + return global.coinFuncs.convertBlob(this.buffer).toString('hex'); + }; + // Make it so you can get the raw block blob out. + this.nextBlobWithChildNonce = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Don't convert the blob to something hashable. You bad. + return this.buffer.toString('hex'); + }; + }; + + this.cryptoNight = multiHashing.cryptonight; + +} + +module.exports = Coin; From 241fbfa004feb86a9e563db462cd984563c8822a Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 11:20:43 -0500 Subject: [PATCH 12/22] Create grf.js --- lib/coins/grf.js | 161 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 lib/coins/grf.js diff --git a/lib/coins/grf.js b/lib/coins/grf.js new file mode 100644 index 00000000..ce638353 --- /dev/null +++ b/lib/coins/grf.js @@ -0,0 +1,161 @@ +"use strict"; +const bignum = require('bignum'); +const cnUtil = require('cryptonote-util'); +const multiHashing = require('multi-hashing'); +const crypto = require('crypto'); +const debug = require('debug')('coinFuncs'); + +let hexChars = new RegExp("[0-9a-f]+"); + +function Coin(data){ + this.bestExchange = global.config.payout.bestExchange; + this.data = data; + let instanceId = crypto.randomBytes(4); + this.coinDevAddress = "GAQsx9YDqkN5WMiGgZ8AqS9S5Wg7HwNFLdPDqjmvwxs8ekKy6L6ph69TFU5uxF1MzySWBwxKPKtDxPLJ7fD6j1Xu3MWhYyS"; // ArqTras + this.poolDevAddress = "GAQsx9YDqkN5WMiGgZ8AqS9S5Wg7HwNFLdPDqjmvwxs8ekKy6L6ph69TFU5uxF1MzySWBwxKPKtDxPLJ7fD6j1Xu3MWhYyS"; // ArqTras + + this.blockedAddresses = [ + this.coinDevAddress, + this.poolDevAddress, + ]; + + this.exchangeAddresses = [ + ]; // These are addresses that MUST have a paymentID to perform logins with. + + this.prefix = 90; + this.intPrefix = 91; + + if (global.config.general.testnet === true){ + this.prefix = 84; + this.intPrefix = 85; + } + + this.supportsAutoExchange = false; + + this.niceHashDiff = 405000; + + this.getBlockHeaderByID = function(blockId, callback){ + global.support.rpcDaemon('getblockheaderbyheight', {"height": blockId}, function (body) { + if (body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockHeaderByHash = function(blockHash, callback){ + global.support.rpcDaemon('getblockheaderbyhash', {"hash": blockHash}, function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getLastBlockHeader = function(callback){ + global.support.rpcDaemon('getlastblockheader', [], function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockTemplate = function(walletAddress, callback){ + global.support.rpcDaemon('getblocktemplate', { + reserve_size: 17, + wallet_address: walletAddress + }, function(body){ + return callback(body); + }); + }; + + this.baseDiff = function(){ + return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); + }; + + this.validateAddress = function(address){ + // This function should be able to be called from the async library, as we need to BLOCK ever so slightly to verify the address. + address = new Buffer(address); + if (cnUtil.address_decode(address) === this.prefix){ + return true; + } + return cnUtil.address_decode_integrated(address) === this.intPrefix; + }; + + this.convertBlob = function(blobBuffer){ + return cnUtil.convert_blob(blobBuffer); + }; + + this.constructNewBlob = function(blockTemplate, NonceBuffer){ + return cnUtil.construct_block_blob(blockTemplate, NonceBuffer); + }; + + this.getBlockID = function(blockBuffer){ + return cnUtil.get_block_id(blockBuffer); + }; + + this.BlockTemplate = function(template) { + /* + Generating a block template is a simple thing. Ask for a boatload of information, and go from there. + Important things to consider. + The reserved space is 13 bytes long now in the following format: + Assuming that the extraNonce starts at byte 130: + |130-133|134-137|138-141|142-145| + |minerNonce/extraNonce - 4 bytes|instanceId - 4 bytes|clientPoolNonce - 4 bytes|clientNonce - 4 bytes| + This is designed to allow a single block template to be used on up to 4 billion poolSlaves (clientPoolNonce) + Each with 4 billion clients. (clientNonce) + While being unique to this particular pool thread (instanceId) + With up to 4 billion clients (minerNonce/extraNonce) + Overkill? Sure. But that's what we do here. Overkill. + */ + + // Set this.blob equal to the BT blob that we get from upstream. + this.blob = template.blocktemplate_blob; + this.idHash = crypto.createHash('md5').update(template.blocktemplate_blob).digest('hex'); + // Set this.diff equal to the known diff for this block. + this.difficulty = template.difficulty; + // Set this.height equal to the known height for this block. + this.height = template.height; + // Set this.reserveOffset to the byte location of the reserved offset. + this.reserveOffset = template.reserved_offset; + // Set this.buffer to the binary decoded version of the BT blob. + this.buffer = new Buffer(this.blob, 'hex'); + // Copy the Instance ID to the reserve offset + 4 bytes deeper. Copy in 4 bytes. + instanceId.copy(this.buffer, this.reserveOffset + 4, 0, 3); + // Generate a clean, shiny new buffer. + this.previous_hash = new Buffer(32); + // Copy in bytes 7 through 39 to this.previous_hash from the current BT. + this.buffer.copy(this.previous_hash, 0, 7, 39); + // Reset the Nonce. - This is the per-miner/pool nonce + this.extraNonce = 0; + // The clientNonceLocation is the location at which the client pools should set the nonces for each of their clients. + this.clientNonceLocation = this.reserveOffset + 12; + // The clientPoolLocation is for multi-thread/multi-server pools to handle the nonce for each of their tiers. + this.clientPoolLocation = this.reserveOffset + 8; + this.nextBlob = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Convert the blob into something hashable. + return global.coinFuncs.convertBlob(this.buffer).toString('hex'); + }; + // Make it so you can get the raw block blob out. + this.nextBlobWithChildNonce = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Don't convert the blob to something hashable. You bad. + return this.buffer.toString('hex'); + }; + }; + + this.cryptoNight = multiHashing.cryptonight; + +} + +module.exports = Coin; From 9652a995ca9ded0a1d643780d31fb8ad0d2ecdb8 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 11:21:13 -0500 Subject: [PATCH 13/22] Create grf.js --- lib/payment_systems/grf.js | 254 +++++++++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 lib/payment_systems/grf.js diff --git a/lib/payment_systems/grf.js b/lib/payment_systems/grf.js new file mode 100644 index 00000000..101ab39b --- /dev/null +++ b/lib/payment_systems/grf.js @@ -0,0 +1,254 @@ +"use strict"; +const async = require("async"); +const debug = require("debug")("payments"); + +let hexChars = new RegExp("[0-9a-f]+"); +let extraPaymentRound = false; +let paymentTimer = null; + +let paymentQueue = async.queue(function (paymentDetails, callback) { + if (paymentTimer !== null){ + clearInterval(paymentTimer); + paymentTimer = null; + } + debug("Making payment based on: " + JSON.stringify(paymentDetails)); + let transferFunc = 'transfer'; + global.support.rpcWallet(transferFunc, paymentDetails, function (body) { + debug("Payment made: " + JSON.stringify(body)); + if (body.hasOwnProperty('error')) { + if (body.error.message === "not enough money"){ + console.error("Issue making payments, not enough money, will try later"); + if(!extraPaymentRound){ + setTimeout(function(){ + makePayments(); + }, global.config.payout.timerRetry * 60 * 1000); + } + extraPaymentRound = true; + return callback(false); + } else { + console.error("Issue making payments" + JSON.stringify(body.error)); + console.error("Will not make more payments until the payment daemon is restarted!"); + //toAddress, subject, body + global.support.sendEmail(global.config.general.adminEmail, "Payment daemon unable to make payment", + "Hello,\r\nThe payment daemon has hit an issue making a payment: " + JSON.stringify(body.error) + + ". Please investigate and restart the payment daemon as appropriate"); + return; + } + } + if (paymentDetails.hasOwnProperty('payment_id')) { + console.log("Payment made to " + paymentDetails.destinations[0].address + " with PaymentID: " + paymentDetails.payment_id + " For: " + global.support.coinToDecimal(paymentDetails.destinations[0].amount) + " GRF with a " + global.support.coinToDecimal(body.result.fee) + " GRF Mining Fee"); + return callback(body.result); + } else { + if (transferFunc === 'transfer') { + console.log("Payment made out to multiple people, total fee: " + global.support.coinToDecimal(body.result.fee) + " GRF"); + } + let intCount = 0; + paymentDetails.destinations.forEach(function (details) { + console.log("Payment made to: " + details.address + " For: " + global.support.coinToDecimal(details.amount) + " GRF"); + intCount += 1; + if (intCount === paymentDetails.destinations.length) { + return callback(body.result); + } + }); + } + }); +}, 1); + +paymentQueue.drain = function(){ + extraPaymentRound = false; + if (global.config.payout.timer > 35791){ + console.error("Payout timer is too high. Please use a value under 35791 to avoid overflows."); + } else { + paymentTimer = setInterval(makePayments, global.config.payout.timer * 60 * 1000); + } + global.database.setCache('lastPaymentCycle', Math.floor(Date.now()/1000)); +}; + +function Payee(amount, address, paymentID, bitcoin) { + this.amount = amount; + this.address = address; + this.paymentID = paymentID; + this.bitcoin = bitcoin; + this.blockID = 0; + this.poolType = ''; + this.transactionID = 0; + this.sqlID = 0; + if (paymentID === null) { + this.id = address; + } else { + this.id = address + "." + paymentID; + } + this.fee = 0; + this.baseFee = global.support.decimalToCoin(global.config.payout.feeSlewAmount); + this.setFeeAmount = function () { + if (this.amount <= global.support.decimalToCoin(global.config.payout.walletMin)) { + this.fee = this.baseFee; + } else if (this.amount <= global.support.decimalToCoin(global.config.payout.feeSlewEnd)) { + let feeValue = this.baseFee / (global.support.decimalToCoin(global.config.payout.feeSlewEnd) - global.support.decimalToCoin(global.config.payout.walletMin)); + this.fee = this.baseFee - ((this.amount - global.support.decimalToCoin(global.config.payout.walletMin)) * feeValue); + } + this.fee = Math.floor(this.fee); + }; + + this.makePaymentWithID = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn, + payment_id: this.paymentID + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let paymentID = this.paymentID; + let payee = this; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, address, paymentID, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.makePaymentAsIntegrated = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let payee = this; + + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?)", + [0, address, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.trackPayment = function () { + global.mysql.query("UPDATE balance SET amount = amount - ? WHERE id = ?", [this.amount, this.sqlID]); + global.mysql.query("INSERT INTO payments (unlocked_time, paid_time, pool_type, payment_address, transaction_id, bitcoin, amount, payment_id, transfer_fee)" + + " VALUES (now(), now(), ?, ?, ?, ?, ?, ?, ?)", [this.poolType, this.address, this.transactionID, this.bitcoin, this.amount - this.fee, this.paymentID, this.fee]); + }; +} + +function makePayments() { + global.mysql.query("SELECT * FROM balance WHERE amount >= ?", [global.support.decimalToCoin(global.config.payout.walletMin)]).then(function (rows) { + console.log("Loaded all payees into the system for processing"); + let paymentDestinations = []; + let totalAmount = 0; + let roundCount = 0; + let payeeList = []; + let payeeObjects = {}; + rows.forEach(function (row) { + debug("Starting round for: " + JSON.stringify(row)); + let payee = new Payee(row.amount, row.payment_address, row.payment_id, row.bitcoin); + payeeObjects[row.payment_address] = payee; + global.mysql.query("SELECT payout_threshold FROM users WHERE username = ?", [payee.id]).then(function (userRow) { + roundCount += 1; + let threshold = 0; + if (userRow.length !== 0) { + threshold = userRow[0].payout_threshold; + } + payee.poolType = row.pool_type; + payee.sqlID = row.id; + if (payee.poolType === "fees" && payee.address === global.config.payout.feeAddress && payee.amount >= ((global.support.decimalToCoin(global.config.payout.feesForTXN) + global.support.decimalToCoin(global.config.payout.exchangeMin)))) { + debug("This is the fee address internal check for value"); + payee.amount -= global.support.decimalToCoin(global.config.payout.feesForTXN); + } else if (payee.address === global.config.payout.feeAddress && payee.poolType === "fees") { + debug("Unable to pay fee address."); + payee.amount = 0; + } + let remainder = payee.amount % (global.config.payout.denom * global.config.general.sigDivisor); + if (remainder !== 0) { + payee.amount -= remainder; + } + if (payee.amount > threshold) { + payee.setFeeAmount(); + if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length !== 106) { + debug("Adding " + payee.id + " to the list of people to pay (OG Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + paymentDestinations.push({amount: payee.amount - payee.fee, address: payee.address}); + totalAmount += payee.amount; + payeeList.push(payee); + } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 0) { + debug("Adding " + payee.id + " to the list of people to pay (Payment ID Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makePaymentWithID(); + } + } + debug("Went: " + roundCount + " With: " + paymentDestinations.length + " Possible destinations and: " + rows.length + " Rows"); + if (roundCount === rows.length && paymentDestinations.length > 0) { + while (paymentDestinations.length > 0) { + let paymentDetails = { + destinations: paymentDestinations.splice(0, global.config.payout.maxPaymentTxns), + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn + }; + console.log("Paying out: " + paymentDetails.destinations.length + " people"); + paymentQueue.push(paymentDetails, function (body) { //jshint ignore:line + // This is the only section that could potentially contain multiple txns. Lets do this safely eh? + if (body.fee && body.fee > 10) { + debug("Made it to the SQL insert for transactions"); + let totalAmount = 0; + paymentDetails.destinations.forEach(function (payeeItem) { + totalAmount += payeeObjects[payeeItem.address].amount; + totalAmount += payeeObjects[payeeItem.address].fee; + }); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, null, null, totalAmount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, paymentDetails.destinations.length]).then(function (result) { + paymentDetails.destinations.forEach(function (payeeItem) { + payee = payeeObjects[payeeItem.address]; + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + } + } + }); + }); + }); +} + +function init() { + global.support.rpcWallet("store", [], function () { + }); + setInterval(function () { + global.support.rpcWallet("store", [], function () { + }); + }, 60000); + console.log("Setting the payment timer to: " + global.config.payout.timer + " minutes with a: " + global.config.payout.timerRetry + " minute delay if the wallet is out of money"); + makePayments(); +} + +init(); From b82e54894cf33e16c77d906c21971f956fce9562 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 11:22:05 -0500 Subject: [PATCH 14/22] Create trtl.js --- lib/payment_systems/trtl.js | 257 ++++++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 lib/payment_systems/trtl.js diff --git a/lib/payment_systems/trtl.js b/lib/payment_systems/trtl.js new file mode 100644 index 00000000..2ebc6e72 --- /dev/null +++ b/lib/payment_systems/trtl.js @@ -0,0 +1,257 @@ +"use strict"; +const async = require("async"); +const debug = require("debug")("payments"); + +let hexChars = new RegExp("[0-9a-f]+"); +let extraPaymentRound = false; +let paymentTimer = null; + +let paymentQueue = async.queue(function (paymentDetails, callback) { + if (paymentTimer !== null){ + clearInterval(paymentTimer); + paymentTimer = null; + } + debug("Making payment based on: " + JSON.stringify(paymentDetails)); + let transferFunc = 'transfer'; + global.support.rpcWallet(transferFunc, paymentDetails, function (body) { + debug("Payment made: " + JSON.stringify(body)); + if (body.hasOwnProperty('error')) { + if (body.error.message === "not enough money"){ + console.error("Issue making payments, not enough money, will try later"); + if(!extraPaymentRound){ + setTimeout(function(){ + makePayments(); + }, global.config.payout.timerRetry * 60 * 1000); + } + extraPaymentRound = true; + return callback(false); + } else { + console.error("Issue making payments" + JSON.stringify(body.error)); + console.error("Will not make more payments until the payment daemon is restarted!"); + //toAddress, subject, body + global.support.sendEmail(global.config.general.adminEmail, "Payment daemon unable to make payment", + "Hello,\r\nThe payment daemon has hit an issue making a payment: " + JSON.stringify(body.error) + + ". Please investigate and restart the payment daemon as appropriate"); + return; + } + } + if (paymentDetails.hasOwnProperty('payment_id')) { + console.log("Payment made to " + paymentDetails.destinations[0].address + " with PaymentID: " + paymentDetails.payment_id + " For: " + global.support.coinToDecimal(paymentDetails.destinations[0].amount) + " XMR with a " + global.support.coinToDecimal(body.result.fee) + " XMR Mining Fee"); + return callback(body.result); + } else { + if (transferFunc === 'transfer') { + console.log("Payment made out to multiple people, total fee: " + global.support.coinToDecimal(body.result.fee) + " XMR"); + } + let intCount = 0; + paymentDetails.destinations.forEach(function (details) { + console.log("Payment made to: " + details.address + " For: " + global.support.coinToDecimal(details.amount) + " XMR"); + intCount += 1; + if (intCount === paymentDetails.destinations.length) { + return callback(body.result); + } + }); + } + }); +}, 1); + +paymentQueue.drain = function(){ + extraPaymentRound = false; + if (global.config.payout.timer > 35791){ + console.error("Payout timer is too high. Please use a value under 35791 to avoid overflows."); + } else { + paymentTimer = setInterval(makePayments, global.config.payout.timer * 60 * 1000); + } + global.database.setCache('lastPaymentCycle', Math.floor(Date.now()/1000)); +}; + +function Payee(amount, address, paymentID, bitcoin) { + this.amount = amount; + this.address = address; + this.paymentID = paymentID; + this.bitcoin = bitcoin; + this.blockID = 0; + this.poolType = ''; + this.transactionID = 0; + this.sqlID = 0; + if (paymentID === null) { + this.id = address; + } else { + this.id = address + "." + paymentID; + } + this.fee = 0; + this.baseFee = global.support.decimalToCoin(global.config.payout.feeSlewAmount); + this.setFeeAmount = function () { + if (this.amount <= global.support.decimalToCoin(global.config.payout.walletMin)) { + this.fee = this.baseFee; + } else if (this.amount <= global.support.decimalToCoin(global.config.payout.feeSlewEnd)) { + let feeValue = this.baseFee / (global.support.decimalToCoin(global.config.payout.feeSlewEnd) - global.support.decimalToCoin(global.config.payout.walletMin)); + this.fee = this.baseFee - ((this.amount - global.support.decimalToCoin(global.config.payout.walletMin)) * feeValue); + } + this.fee = Math.floor(this.fee); + }; + + this.makePaymentWithID = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + fee: global.config.payout.fee, + unlock_time: global.config.payout.unlock_time, + mixin: global.config.payout.mixIn, + payment_id: this.paymentID + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let paymentID = this.paymentID; + let payee = this; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (typeof body.tx_hash !== 'undefined') { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, address, paymentID, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, global.config.payout.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.makePaymentAsIntegrated = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + fee: global.config.payout.fee, + unlock_time: global.config.payout.unlock_time, + mixin: global.config.payout.mixIn + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let payee = this; + + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (typeof body.tx_hash !== 'undefined') { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?)", + [0, address, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, global.config.payout.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.trackPayment = function () { + global.mysql.query("UPDATE balance SET amount = amount - ? WHERE id = ?", [this.amount, this.sqlID]); + global.mysql.query("INSERT INTO payments (unlocked_time, paid_time, pool_type, payment_address, transaction_id, bitcoin, amount, payment_id, transfer_fee)" + + " VALUES (now(), now(), ?, ?, ?, ?, ?, ?, ?)", [this.poolType, this.address, this.transactionID, this.bitcoin, this.amount - this.fee, this.paymentID, this.fee]); + }; +} + +function makePayments() { + global.mysql.query("SELECT * FROM balance WHERE amount >= ?", [global.support.decimalToCoin(global.config.payout.walletMin)]).then(function (rows) { + console.log("Loaded all payees into the system for processing"); + let paymentDestinations = []; + let totalAmount = 0; + let roundCount = 0; + let payeeList = []; + let payeeObjects = {}; + rows.forEach(function (row) { + debug("Starting round for: " + JSON.stringify(row)); + let payee = new Payee(row.amount, row.payment_address, row.payment_id, row.bitcoin); + payeeObjects[row.payment_address] = payee; + global.mysql.query("SELECT payout_threshold FROM users WHERE username = ?", [payee.id]).then(function (userRow) { + roundCount += 1; + let threshold = 0; + if (userRow.length !== 0) { + threshold = userRow[0].payout_threshold; + } + payee.poolType = row.pool_type; + payee.sqlID = row.id; + if (payee.poolType === "fees" && payee.address === global.config.payout.feeAddress && payee.amount >= ((global.support.decimalToCoin(global.config.payout.feesForTXN) + global.support.decimalToCoin(global.config.payout.exchangeMin)))) { + debug("This is the fee address internal check for value"); + payee.amount -= global.support.decimalToCoin(global.config.payout.feesForTXN); + } else if (payee.address === global.config.payout.feeAddress && payee.poolType === "fees") { + debug("Unable to pay fee address."); + payee.amount = 0; + } + let remainder = payee.amount % (global.config.payout.denom * global.config.general.sigDivisor); + if (remainder !== 0) { + payee.amount -= remainder; + } + if (payee.amount > threshold) { + payee.setFeeAmount(); + if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length !== 106) { + debug("Adding " + payee.id + " to the list of people to pay (OG Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + paymentDestinations.push({amount: payee.amount - payee.fee, address: payee.address}); + totalAmount += payee.amount; + payeeList.push(payee); + } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 0) { + debug("Adding " + payee.id + " to the list of people to pay (Payment ID Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makePaymentWithID(); + } + } + debug("Went: " + roundCount + " With: " + paymentDestinations.length + " Possible destinations and: " + rows.length + " Rows"); + if (roundCount === rows.length && paymentDestinations.length > 0) { + while (paymentDestinations.length > 0) { + let paymentDetails = { + destinations: paymentDestinations.splice(0, global.config.payout.maxPaymentTxns), + mixin: global.config.payout.mixIn, + fee: global.config.payout.fee, + unlock_time: global.config.payout.unlock_time + }; + console.log("Paying out: " + paymentDetails.destinations.length + " people"); + paymentQueue.push(paymentDetails, function (body) { //jshint ignore:line + // This is the only section that could potentially contain multiple txns. Lets do this safely eh? + if (typeof body.tx_hash !== 'undefined') { + debug("Made it to the SQL insert for transactions"); + let totalAmount = 0; + paymentDetails.destinations.forEach(function (payeeItem) { + totalAmount += payeeObjects[payeeItem.address].amount; + totalAmount += payeeObjects[payeeItem.address].fee; + }); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, null, null, totalAmount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, global.config.payout.fee, paymentDetails.destinations.length]).then(function (result) { + paymentDetails.destinations.forEach(function (payeeItem) { + payee = payeeObjects[payeeItem.address]; + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + } + } + }); + }); + }); +} + +function init() { + global.support.rpcWallet("store", [], function () { + }); + setInterval(function () { + global.support.rpcWallet("store", [], function () { + }); + }, 60000); + console.log("Setting the payment timer to: " + global.config.payout.timer + " minutes with a: " + global.config.payout.timerRetry + " minute delay if the wallet is out of money"); + makePayments(); +} + +init(); From 3720bd9fdd26096d9ad4f7ce385754769438833f Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 11:22:39 -0500 Subject: [PATCH 15/22] Create trtl.js --- lib/coins/trtl.js | 155 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 lib/coins/trtl.js diff --git a/lib/coins/trtl.js b/lib/coins/trtl.js new file mode 100644 index 00000000..529e708a --- /dev/null +++ b/lib/coins/trtl.js @@ -0,0 +1,155 @@ +"use strict"; +const bignum = require('bignum'); +const cnUtil = require('forknote-util'); +const multiHashing = require('multi-hashing'); +const crypto = require('crypto'); +const debug = require('debug')('coinFuncs'); + +let hexChars = new RegExp("[0-9a-f]+"); + +function Coin(data){ + this.bestExchange = global.config.payout.bestExchange; + this.data = data; + let instanceId = crypto.randomBytes(4); + this.coinDevAddress = ""; // Developer Address + this.poolDevAddress = ""; // Snipa Address + + this.blockedAddresses = [ + this.coinDevAddress, + this.poolDevAddress, + ]; + + this.exchangeAddresses = [ + ]; // These are addresses that MUST have a paymentID to perform logins with. + + this.prefix = 3914525; + + this.supportsAutoExchange = false; + + this.niceHashDiff = 400000; + + this.getBlockHeaderByID = function(blockId, callback){ + global.support.rpcDaemon('getblockheaderbyheight', {"height": blockId}, function (body) { + if (body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockHeaderByHash = function(blockHash, callback){ + global.support.rpcDaemon('getblockheaderbyhash', {"hash": blockHash}, function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getLastBlockHeader = function(callback){ + global.support.rpcDaemon('getlastblockheader', [], function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockTemplate = function(walletAddress, callback){ + global.support.rpcDaemon('getblocktemplate', { + reserve_size: 17, + wallet_address: walletAddress + }, function(body){ + return callback(body); + }); + }; + + this.baseDiff = function(){ + return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); + }; + + this.validateAddress = function(address){ + // This function should be able to be called from the async library, as we need to BLOCK ever so slightly to verify the address. + address = new Buffer(address); + if (cnUtil.address_decode(address) === this.prefix){ + return true; + } +// return cnUtil.address_decode_integrated(address) === this.intPrefix; + }; + + this.convertBlob = function(blobBuffer){ + return cnUtil.convert_blob(blobBuffer); + }; + + this.constructNewBlob = function(blockTemplate, NonceBuffer){ + return cnUtil.construct_block_blob(blockTemplate, NonceBuffer); + }; + + this.getBlockID = function(blockBuffer){ + return cnUtil.get_block_id(blockBuffer); + }; + + this.BlockTemplate = function(template) { + /* + Generating a block template is a simple thing. Ask for a boatload of information, and go from there. + Important things to consider. + The reserved space is 13 bytes long now in the following format: + Assuming that the extraNonce starts at byte 130: + |130-133|134-137|138-141|142-145| + |minerNonce/extraNonce - 4 bytes|instanceId - 4 bytes|clientPoolNonce - 4 bytes|clientNonce - 4 bytes| + This is designed to allow a single block template to be used on up to 4 billion poolSlaves (clientPoolNonce) + Each with 4 billion clients. (clientNonce) + While being unique to this particular pool thread (instanceId) + With up to 4 billion clients (minerNonce/extraNonce) + Overkill? Sure. But that's what we do here. Overkill. + */ + + // Set this.blob equal to the BT blob that we get from upstream. + this.blob = template.blocktemplate_blob; + this.idHash = crypto.createHash('md5').update(template.blocktemplate_blob).digest('hex'); + // Set this.diff equal to the known diff for this block. + this.difficulty = template.difficulty; + // Set this.height equal to the known height for this block. + this.height = template.height; + // Set this.reserveOffset to the byte location of the reserved offset. + this.reserveOffset = template.reserved_offset; + // Set this.buffer to the binary decoded version of the BT blob. + this.buffer = new Buffer(this.blob, 'hex'); + // Copy the Instance ID to the reserve offset + 4 bytes deeper. Copy in 4 bytes. + instanceId.copy(this.buffer, this.reserveOffset + 4, 0, 3); + // Generate a clean, shiny new buffer. + this.previous_hash = new Buffer(32); + // Copy in bytes 7 through 39 to this.previous_hash from the current BT. + this.buffer.copy(this.previous_hash, 0, 7, 39); + // Reset the Nonce. - This is the per-miner/pool nonce + this.extraNonce = 0; + // The clientNonceLocation is the location at which the client pools should set the nonces for each of their clients. + this.clientNonceLocation = this.reserveOffset + 12; + // The clientPoolLocation is for multi-thread/multi-server pools to handle the nonce for each of their tiers. + this.clientPoolLocation = this.reserveOffset + 8; + this.nextBlob = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Convert the blob into something hashable. + return global.coinFuncs.convertBlob(this.buffer).toString('hex'); + }; + // Make it so you can get the raw block blob out. + this.nextBlobWithChildNonce = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Don't convert the blob to something hashable. You bad. + return this.buffer.toString('hex'); + }; + }; + + this.cryptoNight = multiHashing.cryptonight; + +} + +module.exports = Coin; From 654f5f6b5d8e0f066a493da2a2a86e48eed9a8db Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 11:23:11 -0500 Subject: [PATCH 16/22] Create xmv.js --- lib/coins/xmv.js | 169 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 lib/coins/xmv.js diff --git a/lib/coins/xmv.js b/lib/coins/xmv.js new file mode 100644 index 00000000..a4521110 --- /dev/null +++ b/lib/coins/xmv.js @@ -0,0 +1,169 @@ +"use strict"; +const bignum = require('bignum'); +const cnUtil = require('cryptonote-util'); +const multiHashing = require('multi-hashing'); +const crypto = require('crypto'); +const debug = require('debug')('coinFuncs'); + +let hexChars = new RegExp("[0-9a-f]+"); + +function Coin(data){ + this.bestExchange = global.config.payout.bestExchange; + this.data = data; + let instanceId = crypto.randomBytes(4); + this.coinDevAddress = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; // Developer Address + this.poolDevAddress = "44Ldv5GQQhP7K7t3ZBdZjkPA7Kg7dhHwk3ZM3RJqxxrecENSFx27Vq14NAMAd2HBvwEPUVVvydPRLcC69JCZDHLT2X5a4gr"; // Snipa Address + + this.blockedAddresses = [ + this.coinDevAddress, + this.poolDevAddress, + "43SLUTpyTgXCNXsL43uD8FWZ5wLAdX7Ak67BgGp7dxnGhLmrffDTXoeGm2GBRm8JjigN9PTg2gnShQn5gkgE1JGWJr4gsEU", // Wolf0's address + "42QWoLF7pdwMcTXDviJvNkWEHJ4TXnMBh2Cx6HNkVAW57E48Zfw6wLwDUYFDYJAqY7PLJUTz9cHWB5C4wUA7UJPu5wPf4sZ", // Wolf0's address + "46gq64YYgCk88LxAadXbKLeQtCJtsLSD63NiEc3XHLz8NyPAyobACP161JbgyH2SgTau3aPUsFAYyK2RX4dHQoaN1ats6iT", // Claymore's Fee Address. + "47mr7jYTroxQMwdKoPQuJoc9Vs9S9qCUAL6Ek4qyNFWJdqgBZRn4RYY2QjQfqEMJZVWPscupSgaqmUn1dpdUTC4fQsu3yjN" // Claymore's _other_ fee address. + ]; + + this.exchangeAddresses = [ + "46yzCCD3Mza9tRj7aqPSaxVbbePtuAeKzf8Ky2eRtcXGcEgCg1iTBio6N4sPmznfgGEUGDoBz5CLxZ2XPTyZu1yoCAG7zt6", // Shapeshift.io + "463tWEBn5XZJSxLU6uLQnQ2iY9xuNcDbjLSjkn3XAXHCbLrTTErJrBWYgHJQyrCwkNgYvyV3z8zctJLPCZy24jvb3NiTcTJ", // Bittrex + "44TVPcCSHebEQp4LnapPkhb2pondb2Ed7GJJLc6TkKwtSyumUnQ6QzkCCkojZycH2MRfLcujCM7QR1gdnRULRraV4UpB5n4", // Xmr.to + "47sghzufGhJJDQEbScMCwVBimTuq6L5JiRixD8VeGbpjCTA12noXmi4ZyBZLc99e66NtnKff34fHsGRoyZk3ES1s1V4QVcB" // Poloniex + ]; // These are addresses that MUST have a paymentID to perform logins with. + + this.prefix = 18; + this.intPrefix = 19; + + if (global.config.general.testnet === true){ + this.prefix = 53; + this.intPrefix = 54; + } + + this.supportsAutoExchange = true; + + this.niceHashDiff = 400000; + + this.getBlockHeaderByID = function(blockId, callback){ + global.support.rpcDaemon('getblockheaderbyheight', {"height": blockId}, function (body) { + if (body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockHeaderByHash = function(blockHash, callback){ + global.support.rpcDaemon('getblockheaderbyhash', {"hash": blockHash}, function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getLastBlockHeader = function(callback){ + global.support.rpcDaemon('getlastblockheader', [], function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockTemplate = function(walletAddress, callback){ + global.support.rpcDaemon('getblocktemplate', { + reserve_size: 17, + wallet_address: walletAddress + }, function(body){ + return callback(body); + }); + }; + + this.baseDiff = function(){ + return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); + }; + + this.validateAddress = function(address){ + // This function should be able to be called from the async library, as we need to BLOCK ever so slightly to verify the address. + address = new Buffer(address); + if (cnUtil.address_decode(address) === this.prefix){ + return true; + } + return cnUtil.address_decode_integrated(address) === this.intPrefix; + }; + + this.convertBlob = function(blobBuffer){ + return cnUtil.convert_blob(blobBuffer); + }; + + this.constructNewBlob = function(blockTemplate, NonceBuffer){ + return cnUtil.construct_block_blob(blockTemplate, NonceBuffer); + }; + + this.getBlockID = function(blockBuffer){ + return cnUtil.get_block_id(blockBuffer); + }; + + this.BlockTemplate = function(template) { + /* + Generating a block template is a simple thing. Ask for a boatload of information, and go from there. + Important things to consider. + The reserved space is 13 bytes long now in the following format: + Assuming that the extraNonce starts at byte 130: + |130-133|134-137|138-141|142-145| + |minerNonce/extraNonce - 4 bytes|instanceId - 4 bytes|clientPoolNonce - 4 bytes|clientNonce - 4 bytes| + This is designed to allow a single block template to be used on up to 4 billion poolSlaves (clientPoolNonce) + Each with 4 billion clients. (clientNonce) + While being unique to this particular pool thread (instanceId) + With up to 4 billion clients (minerNonce/extraNonce) + Overkill? Sure. But that's what we do here. Overkill. + */ + + // Set this.blob equal to the BT blob that we get from upstream. + this.blob = template.blocktemplate_blob; + this.idHash = crypto.createHash('md5').update(template.blocktemplate_blob).digest('hex'); + // Set this.diff equal to the known diff for this block. + this.difficulty = template.difficulty; + // Set this.height equal to the known height for this block. + this.height = template.height; + // Set this.reserveOffset to the byte location of the reserved offset. + this.reserveOffset = template.reserved_offset; + // Set this.buffer to the binary decoded version of the BT blob. + this.buffer = new Buffer(this.blob, 'hex'); + // Copy the Instance ID to the reserve offset + 4 bytes deeper. Copy in 4 bytes. + instanceId.copy(this.buffer, this.reserveOffset + 4, 0, 3); + // Generate a clean, shiny new buffer. + this.previous_hash = new Buffer(32); + // Copy in bytes 7 through 39 to this.previous_hash from the current BT. + this.buffer.copy(this.previous_hash, 0, 7, 39); + // Reset the Nonce. - This is the per-miner/pool nonce + this.extraNonce = 0; + // The clientNonceLocation is the location at which the client pools should set the nonces for each of their clients. + this.clientNonceLocation = this.reserveOffset + 12; + // The clientPoolLocation is for multi-thread/multi-server pools to handle the nonce for each of their tiers. + this.clientPoolLocation = this.reserveOffset + 8; + this.nextBlob = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Convert the blob into something hashable. + return global.coinFuncs.convertBlob(this.buffer).toString('hex'); + }; + // Make it so you can get the raw block blob out. + this.nextBlobWithChildNonce = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Don't convert the blob to something hashable. You bad. + return this.buffer.toString('hex'); + }; + }; + + this.cryptoNight = multiHashing.cryptonight; + +} + +module.exports = Coin; From cd1909bb98188db400553300b161bd56c5081ac7 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 11:23:48 -0500 Subject: [PATCH 17/22] Create xmv.js --- lib/payment_systems/xmv.js | 696 +++++++++++++++++++++++++++++++++++++ 1 file changed, 696 insertions(+) create mode 100644 lib/payment_systems/xmv.js diff --git a/lib/payment_systems/xmv.js b/lib/payment_systems/xmv.js new file mode 100644 index 00000000..44ea2876 --- /dev/null +++ b/lib/payment_systems/xmv.js @@ -0,0 +1,696 @@ +"use strict"; +const shapeshift = require('shapeshift.io'); +const async = require("async"); +const debug = require("debug")("payments"); +const request = require('request-json'); +const range = require('range'); + +let hexChars = new RegExp("[0-9a-f]+"); +let bestExchange = global.config.payout.bestExchange; +let xmrAPIClient = request.createClient('https://xmr.to/api/v1/xmr2btc/'); +let extraPaymentRound = false; +let paymentTimer = null; + +let shapeshiftQueue = async.queue(function (task, callback) { + // Amount needs to be shifted in as a non-completed value, as the wallet will only take non-complete values.. + let amount = task.amount - task.fee; + // Address is the destination address IN BTC. + let address = task.address; + // PaymentIDs are the paymentID's to flag as paid by this transaction. + // Should be a massive list of ID's so we can bulk-update them, by merging them with 's. + // Here we go! General process: Scan shapeshift for valid amounts of funds to xfer around. + // Once there's enough funds, then we active txn + // Do a wallet call to xfer. + // Setup a monitor on the transaction + async.waterfall([ + function (intCallback) { + // Verify if the coin is active in ShapeShift first. + shapeshift.coins(function (err, coinData) { + if (err) { + intCallback(err); + } else if (!coinData.hasOwnProperty(global.config.general.coinCode) || coinData[global.config.general.coinCode].status !== "available") { + intCallback("Coin " + global.config.general.coinCode + " Is not available at this time on shapeshift."); + } else { + intCallback(null); + } + }); + }, + function (intCallback) { + // Get the market information from shapeshift, which includes deposit limits, minimum deposits, rates, etc. + shapeshift.marketInfo(global.config.payout.shapeshiftPair, function (err, marketInfo) { + if (err) { + intCallback(err); + } else if (!marketInfo.hasOwnProperty("limit") || marketInfo.limit <= global.support.coinToDecimal(amount)) { + intCallback("Not enough coin in shapeshift to process at this time."); + } else if (!marketInfo.hasOwnProperty("min") || marketInfo.min >= global.support.coinToDecimal(amount)) { + intCallback("Not enough coin to hit the shapeshift minimum deposits."); + } else { + intCallback(null, marketInfo); + } + }); + }, + function (marketInfo, intCallback) { + // Validated there's enough coin. Time to make our dank txn. + // Return: + /* + { + "orderId": "cc49c556-e645-4c15-a943-d50a935274e4", + "sAddress": "46yzCCD3Mza9tRj7aqPSaxVbbePtuAeKzf8Ky2eRtcXGcEgCg1iTBio6N4sPmznfgGEUGDoBz5CLxZ2XPTyZu1yoCAG7zt6", + "deposit": "d8041668718e6e9d9d0fd335ee5ecd923e6fd074c41316d041cc18b779ade10e", + "depositType": "XMR", + "withdrawal": "1DbxcoCBSA9N7uZvkcvWxuLxSau9q9Pwiu", + "withdrawalType": "BTC", + "public": null, + "apiPubKey": "shapeshift", + "returnAddress": "46XWBqE1iwsVxSDP1qDrxhE1XvsZV6eALG5LwnoMdjbT4GPdy2bZTb99kagzxp2MMjUamTYZ4WgvZdFadvMimTjvR6Gv8hL", + "returnAddressType": "XMR" + } + Valid Statuses: + "received" + "complete" + "error" + "no_deposits" + Complete State Information: + { + "status": "complete", + "address": "d8041668718e6e9d9d0fd335ee5ecd923e6fd074c41316d041cc18b779ade10e", + "withdraw": "1DbxcoCBSA9N7uZvkcvWxuLxSau9q9Pwiu", + "incomingCoin": 3, + "incomingType": "XMR", + "outgoingCoin": "0.04186155", + "outgoingType": "BTC", + "transaction": "be9d97f6fc75262151f8f63e035c6ed638b9eb2a4e93fef43ea63124b045dbfb" + } + */ + shapeshift.shift(address, global.config.payout.shapeshiftPair, {returnAddress: global.config.pool.address}, function (err, returnData) { + if (err) { + intCallback(err); + } else { + global.mysql.query("INSERT INTO shapeshiftTxn (id, address, paymentID, depositType, withdrawl, withdrawlType, returnAddress, returnAddressType, txnStatus) VALUES (?,?,?,?,?,?,?,?,?)", + [returnData.orderId, returnData.sAddress, returnData.deposit, returnData.depositType, returnData.withdrawl, returnData.withdrawlType, returnData.returnAddress, returnData.returnAddressType, 'no_deposits']).then(function () { + intCallback(null, marketInfo, returnData); + }).catch(function (error) { + intCallback(error); + }); + } + }); + }, + function (marketInfo, shapeshiftTxnData, intCallback) { + // Make the payment to ShapeShift + let paymentDetails = { + destinations: [ + { + amount: amount, + address: shapeshiftTxnData.sAddress + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn, + payment_id: shapeshiftTxnData.deposit + }; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + intCallback(null, marketInfo, shapeshiftTxnData, body); + } else { + intCallback("Unknown error from the wallet."); + } + }); + }, + function (marketInfo, shapeshiftTxnData, body, intCallback) { + // body.tx_hash = XMR transaction hash. + // Need to add transaction. + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees, exchange_rate, exchange_name, exchange_txn_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + [1, address, null, task.amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, global.support.decimalToCoin(marketInfo.minerFee), 1, global.support.decimalToCoin(marketInfo.rate), 'shapeshift', shapeshiftTxnData.orderId]).then(function (result) { + intCallback(null, result.insertId); + }).catch(function (error) { + intCallback(error); + }); + } + ], function (err, result) { + if (err) { + console.error("Error processing shapeshift txn: " + JSON.stringify(err)); + callback(true); + } else { + // Need to fill out this data pronto! + console.log("Processed ShapeShift transaction for: " + address + " Paid out: " + result + " payments in the db"); + callback(null, result); + } + }); +}, 2); + +let xmrToQueue = async.queue(function (task, callback) { + // http://xmrto-api.readthedocs.io/en/latest/introduction.html + // Documentation looks good! + // Amount needs to be shifted in as a non-completed value, as the wallet will only take non-complete values.. + let amount = task.amount - task.fee; + // Address is the destination address IN BTC. + let address = task.address; + // PaymentIDs are the paymentID's to flag as paid by this transaction. + // Should be a massive list of ID's so we can bulk-update them, by merging them with 's. + // Here we go! General process: Scan shapeshift for valid amounts of funds to xfer around. + // Once there's enough funds, then we active txn + // Do a wallet call to xfer. + // Setup a monitor on the transaction + async.waterfall([ + function (intCallback) { + // Verify if XMR.to is ready to get to work. + xmrAPIClient.get('order_parameter_query/', function (err, res, body) { + if (err) { + return intCallback(err); + } else if (body.error_msg) { + return intCallback(body.error_msg); + } else { + let amtOfBTC = ((amount / global.config.general.sigDivisor) * body.price).toPrecision(5); + console.log("Attempting to pay: " + address + " Amount: " + amtOfBTC + " BTC or " + amount / global.config.general.sigDivisor + " XMR"); + console.log("Response from XMR.to: " + JSON.stringify(body)); + if (body.lower_limit >= amtOfBTC) { + return intCallback("Not enough XMR to hit the minimum deposit"); + } else if (body.upper_limit <= amtOfBTC) { + return intCallback("Too much XMR to pay out to xmr.to"); + } else { + return intCallback(null, amtOfBTC); + } + } + }); + }, + function (btcValue, intCallback) { + // Validated there's enough coin. Time to make our dank txn. + // Return: + /* + { + "state": "TO_BE_CREATED", + "btc_amount": , + "btc_dest_address": "", + "uuid": "" + } + Valid Statuses: + "TO_BE_CREATED" + "UNPAID" + "UNDERPAID" + "PAID_UNCONFIRMED" + "PAID" + "BTC_SENT" + "TIMED_OUT" + "NOT_FOUND" + // Create, then immediately update with the new information w/ a status call. + */ + console.log("Amount of BTC to pay: " + btcValue); + xmrAPIClient.post('order_create/', { + btc_amount: btcValue, + btc_dest_address: address + }, function (err, res, body) { + if (err) { + return intCallback(err); + } else if (body.error_msg) { + return intCallback(body.error_msg); + } else { + return intCallback(null, body.uuid); + } + }); + }, + function (txnID, intCallback) { + // This function only exists because xmr.to is a pretty little fucking princess. + async.doUntil(function (xmrCallback) { + xmrAPIClient.post('order_status_query/', {uuid: txnID}, function (err, res, body) { + if (err) { + return intCallback(err); + } else if (body.error_msg) { + return intCallback(body.error_msg); + } else { + xmrCallback(null, body.state); + } + }); + }, + function (xmrCallback) { + return xmrCallback !== "TO_BE_CREATED"; + }, + function () { + intCallback(null, txnID); + }); + }, + function (txnID, intCallback) { + xmrAPIClient.post('order_status_query/', {uuid: txnID}, function (err, res, body) { + if (err) { + return intCallback(err); + } else if (body.error_msg) { + return intCallback(body.error_msg); + } else { + console.log(JSON.stringify(body)); + global.mysql.query("INSERT INTO xmrtoTxn (id, address, paymentID, depositType, withdrawl, withdrawlType, returnAddress, returnAddressType, txnStatus, amountDeposited, amountSent) VALUES (?,?,?,?,?,?,?,?,?,?,?)", + [txnID, body.xmr_receiving_address, body.xmr_required_payment_id, 'XMR', body.btc_dest_address, 'BTC', global.config.pool.address, 'XMR', body.state_str, global.support.decimalToCoin(body.xmr_amount_total), global.support.decimalToCoin(body.btc_amount)]).then(function () { + return intCallback(null, body, global.support.decimalToCoin(body.xmr_amount_total)); + }).catch(function (error) { + return intCallback(error); + }); + } + }); + }, + function (orderStatus, xmrDeposit, intCallback) { + // Make the payment to ShapeShift + let paymentDetails = { + destinations: [ + { + amount: xmrDeposit, + address: orderStatus.xmr_receiving_address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn, + payment_id: orderStatus.xmr_required_payment_id + }; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + return intCallback(null, orderStatus, body); + } else { + return intCallback("Unknown error from the wallet."); + } + }); + }, + function (orderStatus, body, intCallback) { + // body.tx_hash = XMR transaction hash. + // Need to add transaction. + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees, exchange_rate, exchange_name, exchange_txn_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + [1, address, null, global.support.decimalToCoin(orderStatus.xmr_amount_total), body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1, global.support.decimalToCoin(orderStatus.xmr_price_btc), 'xmrto', orderStatus.uuid]).then(function (result) { + return intCallback(null, result.insertId); + }).catch(function (error) { + return intCallback(error); + }); + } + ], function (err, result) { + if (err) { + console.error("Error processing XMRTo txn: " + JSON.stringify(err)); + return callback("Error!"); + } else { + // Need to fill out this data pronto! + console.log("Processed XMRTo transaction for: " + address + " Paid out: " + result + " payments in the db"); + return callback(null, result); + } + }); +}, 2); + +let paymentQueue = async.queue(function (paymentDetails, callback) { + /* + support JSON URI: http://10.0.0.2:28082/json_rpc Args: {"id":"0","jsonrpc":"2.0","method":"transfer","params":{"destinations":[{"amount":68130252045355,"address":"A2MSrn49ziBPJBh8ZNEhhbfyLMou6mao4C1F5TLGUatmUnCxZArDYkcbAnVkVEopWVeak2rKDrmc8JpoS7n5dvfN9YDPBTG"}],"mixin":4,"payment_id":"7e52c5266de9fede7fb3abc0cd88f937b38b51426f7b34ff99729d28ce4e1142"}} +1ms + payments Payment made: {"id":"0","jsonrpc":"2.0","result":{"fee":40199391255,"tx_hash":"c418708643f72635edf522490bfb2cae9d42a6dc1df30dcde844862dfd88f5b3","tx_key":""}} +2s + */ + if (paymentTimer !== null){ + clearInterval(paymentTimer); + paymentTimer = null; + } + debug("Making payment based on: " + JSON.stringify(paymentDetails)); + let transferFunc = 'transfer'; + global.support.rpcWallet(transferFunc, paymentDetails, function (body) { + debug("Payment made: " + JSON.stringify(body)); + if (body.hasOwnProperty('error')) { + if (body.error.message === "not enough money"){ + console.error("Issue making payments, not enough money, will try later"); + if(!extraPaymentRound){ + setTimeout(function(){ + makePayments(); + }, global.config.payout.timerRetry * 60 * 1000); + } + extraPaymentRound = true; + return callback(false); + } else { + console.error("Issue making payments" + JSON.stringify(body.error)); + console.error("Will not make more payments until the payment daemon is restarted!"); + //toAddress, subject, body + global.support.sendEmail(global.config.general.adminEmail, "Payment daemon unable to make payment", + "Hello,\r\nThe payment daemon has hit an issue making a payment: " + JSON.stringify(body.error) + + ". Please investigate and restart the payment daemon as appropriate"); + return; + } + } + if (paymentDetails.hasOwnProperty('payment_id')) { + console.log("Payment made to " + paymentDetails.destinations[0].address + " with PaymentID: " + paymentDetails.payment_id + " For: " + global.support.coinToDecimal(paymentDetails.destinations[0].amount) + " XMR with a " + global.support.coinToDecimal(body.result.fee) + " XMR Mining Fee"); + return callback(body.result); + } else { + if (transferFunc === 'transfer') { + console.log("Payment made out to multiple people, total fee: " + global.support.coinToDecimal(body.result.fee) + " XMR"); + } + let intCount = 0; + paymentDetails.destinations.forEach(function (details) { + console.log("Payment made to: " + details.address + " For: " + global.support.coinToDecimal(details.amount) + " XMR"); + intCount += 1; + if (intCount === paymentDetails.destinations.length) { + return callback(body.result); + } + }); + } + }); +}, 1); + +paymentQueue.drain = function(){ + extraPaymentRound = false; + if (global.config.payout.timer > 35791){ + console.error("Payout timer is too high. Please use a value under 35791 to avoid overflows."); + } else { + paymentTimer = setInterval(makePayments, global.config.payout.timer * 60 * 1000); + } + global.database.setCache('lastPaymentCycle', Math.floor(Date.now()/1000)); +}; + +function updateShapeshiftCompletion() { + global.mysql.query("SELECT * FROM shapeshiftTxn WHERE txnStatus NOT IN ('complete', 'error')").then(function (rows) { + rows.forEach(function (row) { + shapeshift.status(row.paymentID, function (err, status, returnData) { + if (err) { + return; + } + global.mysql.query("UPDATE shapeshiftTxn SET txnStatus = ? WHERE id = ?", [status, row.id]).then(function () { + if (status === 'complete') { + global.mysql.query("UPDATE shapeshiftTxn SET amountDeposited = ?, amountSent = ?, transactionHash = ? WHERE id = ?", + [global.support.decimalToCoin(returnData.incomingCoin), global.support.bitcoinDecimalToCoin(returnData.outgoingCoin), returnData.transaction, row.id]).then(function () { + global.mysql.query("UPDATE transactions SET confirmed = 1, confirmed_time = now(), btc_amt = ? WHERE exchange_txn_id = ?", [global.support.bitcoinDecimalToCoin(returnData.outgoingCoin), row.id]); + }); + } else if (status === 'error') { + // Failed txn. Need to rollback and delete all related data. Here we go! + global.mysql.query("DELETE FROM shapeshiftTxn WHERE id = ?", [row.id]); + global.mysql.query("SELECT id, xmr_amt, address FROM transactions WHERE exchange_txn_id = ?", [row.id]).then(function (rows) { + global.mysql.query("DELETE FROM transactions WHERE id = ?", [rows[0].id]); + global.mysql.query("DELETE payments WHERE transaction_id = ?", [rows[0].id]); + global.mysql.query("UPDATE balance SET amount = amount+? WHERE payment_address = ? limit 1", [rows[0].xmr_amt, rows[0].address]); + }); + console.error("Failed transaction from ShapeShift " + JSON.stringify(returnData)); + } + }); + }); + }); + }); +} + +function updateXMRToCompletion() { + global.mysql.query("SELECT * FROM xmrtoTxn WHERE txnStatus NOT IN ('PAID', 'TIMED_OUT', 'NOT_FOUND', 'BTC_SENT')").then(function (rows) { + rows.forEach(function (row) { + xmrAPIClient.post('order_status_query/', {uuid: row.id}, function (err, res, body) { + if (err) { + console.log("Error in getting order status: " + JSON.stringify(err)); + return; + } + if (body.error_msg) { + console.log("Error in getting order status: " + body.error_msg); + return; + } + global.mysql.query("UPDATE xmrtoTxn SET txnStatus = ? WHERE id = ?", [body.state, row.id]).then(function () { + if (body.status === 'BTC_SENT') { + global.mysql.query("UPDATE xmrtoTxn SET transactionHash = ? WHERE id = ?", [body.btc_transaction_id, row.id]).then(function () { + global.mysql.query("UPDATE transactions SET confirmed = 1, confirmed_time = now(), btc_amt = ? WHERE exchange_txn_id = ?", [global.support.bitcoinDecimalToCoin(body.btc_amount), row.id]); + }); + } else if (body.status === 'TIMED_OUT' || body.status === 'NOT_FOUND') { + global.mysql.query("DELETE FROM xmrtoTxn WHERE id = ?", [row.id]); + global.mysql.query("SELECT id, xmr_amt, address FROM transactions WHERE exchange_txn_id = ?", [row.id]).then(function (rows) { + global.mysql.query("DELETE FROM transactions WHERE id = ?", [rows[0].id]); + global.mysql.query("DELETE payments WHERE transaction_id = ?", [rows[0].id]); + global.mysql.query("UPDATE balance SET amount = amount+? WHERE payment_address = ? limit 1", [rows[0].xmr_amt, rows[0].address]); + }); + console.error("Failed transaction from XMRto " + JSON.stringify(body)); + } + }); + }); + }); + }); +} + +function determineBestExchange() { + async.waterfall([ + function (callback) { + // Verify if the coin is active in ShapeShift first. + shapeshift.coins(function (err, coinData) { + if (err) { + return callback(err); + } else if (!coinData.hasOwnProperty(global.config.general.coinCode) || coinData[global.config.general.coinCode].status !== "available") { + return callback("Coin " + global.config.general.coinCode + " Is not available at this time on shapeshift."); + } else { + return callback(null); + } + }); + }, + function (callback) { + // Get the market information from shapeshift, which includes deposit limits, minimum deposits, rates, etc. + shapeshift.marketInfo(global.config.payout.shapeshiftPair, function (err, marketInfo) { + if (err) { + return callback(err); + } else if (!marketInfo.hasOwnProperty("rate")) { + return callback("Shapeshift did not return the rate."); + } else { + return callback(null, global.support.bitcoinDecimalToCoin(marketInfo.rate)); + } + }); + }, + function (ssValue, callback) { + xmrAPIClient.get('order_parameter_query/', function (err, res, body) { + console.log("XMR.to pricing body: " + JSON.stringify(body)); + if (err) { + return callback(err); + } else if (body.error_msg) { + return callback(body.error_msg); + } else { + return callback(null, ssValue, global.support.bitcoinDecimalToCoin(body.price)); + } + }); + } + ], function (err, ssValue, xmrToValue) { + if (err) { + return console.error("Error processing exchange value: " + JSON.stringify(err)); + } + debug("ShapeShift Value: " + global.support.bitcoinCoinToDecimal(ssValue) + " XMR.to Value: " + global.support.bitcoinCoinToDecimal(xmrToValue)); + if (ssValue >= xmrToValue) { + console.log("ShapeShift is the better BTC exchange, current rate: " + global.support.bitcoinCoinToDecimal(ssValue)); + bestExchange = 'shapeshift'; + global.mysql.query("UPDATE config SET item_value = 'shapeshift' where item='bestExchange'"); + global.mysql.query("UPDATE config SET item_value = ? where item='exchangeRate'", [ssValue]); + } else { + console.log("XMR.to is the better BTC exchange, current rate: " + global.support.bitcoinCoinToDecimal(xmrToValue)); + bestExchange = 'xmrto'; + global.mysql.query("UPDATE config SET item_value = 'xmrto' where item='bestExchange'"); + global.mysql.query("UPDATE config SET item_value = ? where item='exchangeRate'", [xmrToValue]); + } + }); +} + +function Payee(amount, address, paymentID, bitcoin) { + this.amount = amount; + this.address = address; + this.paymentID = paymentID; + this.bitcoin = bitcoin; + this.blockID = 0; + this.poolType = ''; + this.transactionID = 0; + this.sqlID = 0; + if (paymentID === null) { + this.id = address; + } else { + this.id = address + "." + paymentID; + } + this.fee = 0; + this.baseFee = global.support.decimalToCoin(global.config.payout.feeSlewAmount); + this.setFeeAmount = function () { + if (this.amount <= global.support.decimalToCoin(global.config.payout.walletMin)) { + this.fee = this.baseFee; + } else if (this.amount <= global.support.decimalToCoin(global.config.payout.feeSlewEnd)) { + let feeValue = this.baseFee / (global.support.decimalToCoin(global.config.payout.feeSlewEnd) - global.support.decimalToCoin(global.config.payout.walletMin)); + this.fee = this.baseFee - ((this.amount - global.support.decimalToCoin(global.config.payout.walletMin)) * feeValue); + } + this.fee = Math.floor(this.fee); + }; + + this.makePaymentWithID = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn, + payment_id: this.paymentID + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let paymentID = this.paymentID; + let payee = this; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, address, paymentID, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.makePaymentAsIntegrated = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let payee = this; + + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?)", + [0, address, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.makeBitcoinPayment = function () { + let functionalData = {address: this.address, amount: this.amount, fee: this.fee}; + let payee = this; + if (bestExchange === 'xmrto') { + xmrToQueue.push(functionalData, function (err, transactionID) { + if (err) { + return console.error("Error processing payment for " + functionalData.address); + } + payee.transactionID = transactionID; + payee.trackPayment(); + }); + } else { + shapeshiftQueue.push(functionalData, function (err, transactionID) { + if (err) { + return console.error("Error processing payment for " + functionalData.address); + } + payee.transactionID = transactionID; + payee.trackPayment(); + }); + } + }; + + this.trackPayment = function () { + global.mysql.query("UPDATE balance SET amount = amount - ? WHERE id = ?", [this.amount, this.sqlID]); + global.mysql.query("INSERT INTO payments (unlocked_time, paid_time, pool_type, payment_address, transaction_id, bitcoin, amount, payment_id, transfer_fee)" + + " VALUES (now(), now(), ?, ?, ?, ?, ?, ?, ?)", [this.poolType, this.address, this.transactionID, this.bitcoin, this.amount - this.fee, this.paymentID, this.fee]); + }; +} + +function makePayments() { + global.mysql.query("SELECT * FROM balance WHERE amount >= ?", [global.support.decimalToCoin(global.config.payout.walletMin)]).then(function (rows) { + console.log("Loaded all payees into the system for processing"); + let paymentDestinations = []; + let totalAmount = 0; + let roundCount = 0; + let payeeList = []; + let payeeObjects = {}; + rows.forEach(function (row) { + debug("Starting round for: " + JSON.stringify(row)); + let payee = new Payee(row.amount, row.payment_address, row.payment_id, row.bitcoin); + payeeObjects[row.payment_address] = payee; + global.mysql.query("SELECT payout_threshold FROM users WHERE username = ?", [payee.id]).then(function (userRow) { + roundCount += 1; + let threshold = 0; + if (userRow.length !== 0) { + threshold = userRow[0].payout_threshold; + } + payee.poolType = row.pool_type; + payee.sqlID = row.id; + if (payee.poolType === "fees" && payee.address === global.config.payout.feeAddress && payee.amount >= ((global.support.decimalToCoin(global.config.payout.feesForTXN) + global.support.decimalToCoin(global.config.payout.exchangeMin)))) { + debug("This is the fee address internal check for value"); + payee.amount -= global.support.decimalToCoin(global.config.payout.feesForTXN); + } else if (payee.address === global.config.payout.feeAddress && payee.poolType === "fees") { + debug("Unable to pay fee address."); + payee.amount = 0; + } + let remainder = payee.amount % (global.config.payout.denom * global.config.general.sigDivisor); + if (remainder !== 0) { + payee.amount -= remainder; + } + if (payee.amount > threshold) { + payee.setFeeAmount(); + if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length !== 106) { + debug("Adding " + payee.id + " to the list of people to pay (OG Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + paymentDestinations.push({amount: payee.amount - payee.fee, address: payee.address}); + totalAmount += payee.amount; + payeeList.push(payee); + } else if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length === 106 && (payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0))) { + // Special code to handle integrated payment addresses. What a pain in the rear. + // These are exchange addresses though, so they need to hit the exchange payout amount. + debug("Adding " + payee.id + " to the list of people to pay (Integrated Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makePaymentAsIntegrated(); + } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 0) { + debug("Adding " + payee.id + " to the list of people to pay (Payment ID Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makePaymentWithID(); + } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 1) { + debug("Adding " + payee.id + " to the list of people to pay (Bitcoin Payout). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makeBitcoinPayment(); + } + } + debug("Went: " + roundCount + " With: " + paymentDestinations.length + " Possible destinations and: " + rows.length + " Rows"); + if (roundCount === rows.length && paymentDestinations.length > 0) { + while (paymentDestinations.length > 0) { + let paymentDetails = { + destinations: paymentDestinations.splice(0, global.config.payout.maxPaymentTxns), + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn + }; + console.log("Paying out: " + paymentDetails.destinations.length + " people"); + paymentQueue.push(paymentDetails, function (body) { //jshint ignore:line + // This is the only section that could potentially contain multiple txns. Lets do this safely eh? + if (body.fee && body.fee > 10) { + debug("Made it to the SQL insert for transactions"); + let totalAmount = 0; + paymentDetails.destinations.forEach(function (payeeItem) { + totalAmount += payeeObjects[payeeItem.address].amount; + totalAmount += payeeObjects[payeeItem.address].fee; + }); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, null, null, totalAmount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, paymentDetails.destinations.length]).then(function (result) { + paymentDetails.destinations.forEach(function (payeeItem) { + payee = payeeObjects[payeeItem.address]; + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + } + } + }); + }); + }); +} + +function init() { + global.support.rpcWallet("store", [], function () { + }); + if (global.config.allowBitcoin) { + determineBestExchange(); + setInterval(updateXMRToCompletion, 90000); + setInterval(updateShapeshiftCompletion, 90000); + setInterval(determineBestExchange, 60000); + } + setInterval(function () { + global.support.rpcWallet("store", [], function () { + }); + }, 60000); + console.log("Setting the payment timer to: " + global.config.payout.timer + " minutes with a: " + global.config.payout.timerRetry + " minute delay if the wallet is out of money"); + makePayments(); +} + +init(); From 0d02b8a7802983f5e3f751755e5b240b47090f38 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 11:30:44 -0500 Subject: [PATCH 18/22] Create msr.js --- lib/payment_systems/msr.js | 254 +++++++++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 lib/payment_systems/msr.js diff --git a/lib/payment_systems/msr.js b/lib/payment_systems/msr.js new file mode 100644 index 00000000..3a98c55f --- /dev/null +++ b/lib/payment_systems/msr.js @@ -0,0 +1,254 @@ +"use strict"; +const async = require("async"); +const debug = require("debug")("payments"); + +let hexChars = new RegExp("[0-9a-f]+"); +let extraPaymentRound = false; +let paymentTimer = null; + +let paymentQueue = async.queue(function (paymentDetails, callback) { + if (paymentTimer !== null){ + clearInterval(paymentTimer); + paymentTimer = null; + } + debug("Making payment based on: " + JSON.stringify(paymentDetails)); + let transferFunc = 'transfer'; + global.support.rpcWallet(transferFunc, paymentDetails, function (body) { + debug("Payment made: " + JSON.stringify(body)); + if (body.hasOwnProperty('error')) { + if (body.error.message === "not enough money"){ + console.error("Issue making payments, not enough money, will try later"); + if(!extraPaymentRound){ + setTimeout(function(){ + makePayments(); + }, global.config.payout.timerRetry * 60 * 1000); + } + extraPaymentRound = true; + return callback(false); + } else { + console.error("Issue making payments" + JSON.stringify(body.error)); + console.error("Will not make more payments until the payment daemon is restarted!"); + //toAddress, subject, body + global.support.sendEmail(global.config.general.adminEmail, "Payment daemon unable to make payment", + "Hello,\r\nThe payment daemon has hit an issue making a payment: " + JSON.stringify(body.error) + + ". Please investigate and restart the payment daemon as appropriate"); + return; + } + } + if (paymentDetails.hasOwnProperty('payment_id')) { + console.log("Payment made to " + paymentDetails.destinations[0].address + " with PaymentID: " + paymentDetails.payment_id + " For: " + global.support.coinToDecimal(paymentDetails.destinations[0].amount) + " XMR with a " + global.support.coinToDecimal(body.result.fee) + " XMR Mining Fee"); + return callback(body.result); + } else { + if (transferFunc === 'transfer') { + console.log("Payment made out to multiple people, total fee: " + global.support.coinToDecimal(body.result.fee) + " XMR"); + } + let intCount = 0; + paymentDetails.destinations.forEach(function (details) { + console.log("Payment made to: " + details.address + " For: " + global.support.coinToDecimal(details.amount) + " XMR"); + intCount += 1; + if (intCount === paymentDetails.destinations.length) { + return callback(body.result); + } + }); + } + }); +}, 1); + +paymentQueue.drain = function(){ + extraPaymentRound = false; + if (global.config.payout.timer > 35791){ + console.error("Payout timer is too high. Please use a value under 35791 to avoid overflows."); + } else { + paymentTimer = setInterval(makePayments, global.config.payout.timer * 60 * 1000); + } + global.database.setCache('lastPaymentCycle', Math.floor(Date.now()/1000)); +}; + +function Payee(amount, address, paymentID, bitcoin) { + this.amount = amount; + this.address = address; + this.paymentID = paymentID; + this.bitcoin = bitcoin; + this.blockID = 0; + this.poolType = ''; + this.transactionID = 0; + this.sqlID = 0; + if (paymentID === null) { + this.id = address; + } else { + this.id = address + "." + paymentID; + } + this.fee = 0; + this.baseFee = global.support.decimalToCoin(global.config.payout.feeSlewAmount); + this.setFeeAmount = function () { + if (this.amount <= global.support.decimalToCoin(global.config.payout.walletMin)) { + this.fee = this.baseFee; + } else if (this.amount <= global.support.decimalToCoin(global.config.payout.feeSlewEnd)) { + let feeValue = this.baseFee / (global.support.decimalToCoin(global.config.payout.feeSlewEnd) - global.support.decimalToCoin(global.config.payout.walletMin)); + this.fee = this.baseFee - ((this.amount - global.support.decimalToCoin(global.config.payout.walletMin)) * feeValue); + } + this.fee = Math.floor(this.fee); + }; + + this.makePaymentWithID = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn, + payment_id: this.paymentID + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let paymentID = this.paymentID; + let payee = this; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, address, paymentID, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.makePaymentAsIntegrated = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let payee = this; + + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?)", + [0, address, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.trackPayment = function () { + global.mysql.query("UPDATE balance SET amount = amount - ? WHERE id = ?", [this.amount, this.sqlID]); + global.mysql.query("INSERT INTO payments (unlocked_time, paid_time, pool_type, payment_address, transaction_id, bitcoin, amount, payment_id, transfer_fee)" + + " VALUES (now(), now(), ?, ?, ?, ?, ?, ?, ?)", [this.poolType, this.address, this.transactionID, this.bitcoin, this.amount - this.fee, this.paymentID, this.fee]); + }; +} + +function makePayments() { + global.mysql.query("SELECT * FROM balance WHERE amount >= ?", [global.support.decimalToCoin(global.config.payout.walletMin)]).then(function (rows) { + console.log("Loaded all payees into the system for processing"); + let paymentDestinations = []; + let totalAmount = 0; + let roundCount = 0; + let payeeList = []; + let payeeObjects = {}; + rows.forEach(function (row) { + debug("Starting round for: " + JSON.stringify(row)); + let payee = new Payee(row.amount, row.payment_address, row.payment_id, row.bitcoin); + payeeObjects[row.payment_address] = payee; + global.mysql.query("SELECT payout_threshold FROM users WHERE username = ?", [payee.id]).then(function (userRow) { + roundCount += 1; + let threshold = 0; + if (userRow.length !== 0) { + threshold = userRow[0].payout_threshold; + } + payee.poolType = row.pool_type; + payee.sqlID = row.id; + if (payee.poolType === "fees" && payee.address === global.config.payout.feeAddress && payee.amount >= ((global.support.decimalToCoin(global.config.payout.feesForTXN) + global.support.decimalToCoin(global.config.payout.exchangeMin)))) { + debug("This is the fee address internal check for value"); + payee.amount -= global.support.decimalToCoin(global.config.payout.feesForTXN); + } else if (payee.address === global.config.payout.feeAddress && payee.poolType === "fees") { + debug("Unable to pay fee address."); + payee.amount = 0; + } + let remainder = payee.amount % (global.config.payout.denom * global.config.general.sigDivisor); + if (remainder !== 0) { + payee.amount -= remainder; + } + if (payee.amount > threshold) { + payee.setFeeAmount(); + if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length !== 106) { + debug("Adding " + payee.id + " to the list of people to pay (OG Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + paymentDestinations.push({amount: payee.amount - payee.fee, address: payee.address}); + totalAmount += payee.amount; + payeeList.push(payee); + } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 0) { + debug("Adding " + payee.id + " to the list of people to pay (Payment ID Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makePaymentWithID(); + } + } + debug("Went: " + roundCount + " With: " + paymentDestinations.length + " Possible destinations and: " + rows.length + " Rows"); + if (roundCount === rows.length && paymentDestinations.length > 0) { + while (paymentDestinations.length > 0) { + let paymentDetails = { + destinations: paymentDestinations.splice(0, global.config.payout.maxPaymentTxns), + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn + }; + console.log("Paying out: " + paymentDetails.destinations.length + " people"); + paymentQueue.push(paymentDetails, function (body) { //jshint ignore:line + // This is the only section that could potentially contain multiple txns. Lets do this safely eh? + if (body.fee && body.fee > 10) { + debug("Made it to the SQL insert for transactions"); + let totalAmount = 0; + paymentDetails.destinations.forEach(function (payeeItem) { + totalAmount += payeeObjects[payeeItem.address].amount; + totalAmount += payeeObjects[payeeItem.address].fee; + }); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, null, null, totalAmount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, paymentDetails.destinations.length]).then(function (result) { + paymentDetails.destinations.forEach(function (payeeItem) { + payee = payeeObjects[payeeItem.address]; + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + } + } + }); + }); + }); +} + +function init() { + global.support.rpcWallet("store", [], function () { + }); + setInterval(function () { + global.support.rpcWallet("store", [], function () { + }); + }, 60000); + console.log("Setting the payment timer to: " + global.config.payout.timer + " minutes with a: " + global.config.payout.timerRetry + " minute delay if the wallet is out of money"); + makePayments(); +} + +init(); From 3484ebcf7ef65c9fd92037e8e2c69535ea3a62d6 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 11:33:53 -0500 Subject: [PATCH 19/22] Create omb.js --- lib/payment_systems/omb.js | 254 +++++++++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 lib/payment_systems/omb.js diff --git a/lib/payment_systems/omb.js b/lib/payment_systems/omb.js new file mode 100644 index 00000000..3a98c55f --- /dev/null +++ b/lib/payment_systems/omb.js @@ -0,0 +1,254 @@ +"use strict"; +const async = require("async"); +const debug = require("debug")("payments"); + +let hexChars = new RegExp("[0-9a-f]+"); +let extraPaymentRound = false; +let paymentTimer = null; + +let paymentQueue = async.queue(function (paymentDetails, callback) { + if (paymentTimer !== null){ + clearInterval(paymentTimer); + paymentTimer = null; + } + debug("Making payment based on: " + JSON.stringify(paymentDetails)); + let transferFunc = 'transfer'; + global.support.rpcWallet(transferFunc, paymentDetails, function (body) { + debug("Payment made: " + JSON.stringify(body)); + if (body.hasOwnProperty('error')) { + if (body.error.message === "not enough money"){ + console.error("Issue making payments, not enough money, will try later"); + if(!extraPaymentRound){ + setTimeout(function(){ + makePayments(); + }, global.config.payout.timerRetry * 60 * 1000); + } + extraPaymentRound = true; + return callback(false); + } else { + console.error("Issue making payments" + JSON.stringify(body.error)); + console.error("Will not make more payments until the payment daemon is restarted!"); + //toAddress, subject, body + global.support.sendEmail(global.config.general.adminEmail, "Payment daemon unable to make payment", + "Hello,\r\nThe payment daemon has hit an issue making a payment: " + JSON.stringify(body.error) + + ". Please investigate and restart the payment daemon as appropriate"); + return; + } + } + if (paymentDetails.hasOwnProperty('payment_id')) { + console.log("Payment made to " + paymentDetails.destinations[0].address + " with PaymentID: " + paymentDetails.payment_id + " For: " + global.support.coinToDecimal(paymentDetails.destinations[0].amount) + " XMR with a " + global.support.coinToDecimal(body.result.fee) + " XMR Mining Fee"); + return callback(body.result); + } else { + if (transferFunc === 'transfer') { + console.log("Payment made out to multiple people, total fee: " + global.support.coinToDecimal(body.result.fee) + " XMR"); + } + let intCount = 0; + paymentDetails.destinations.forEach(function (details) { + console.log("Payment made to: " + details.address + " For: " + global.support.coinToDecimal(details.amount) + " XMR"); + intCount += 1; + if (intCount === paymentDetails.destinations.length) { + return callback(body.result); + } + }); + } + }); +}, 1); + +paymentQueue.drain = function(){ + extraPaymentRound = false; + if (global.config.payout.timer > 35791){ + console.error("Payout timer is too high. Please use a value under 35791 to avoid overflows."); + } else { + paymentTimer = setInterval(makePayments, global.config.payout.timer * 60 * 1000); + } + global.database.setCache('lastPaymentCycle', Math.floor(Date.now()/1000)); +}; + +function Payee(amount, address, paymentID, bitcoin) { + this.amount = amount; + this.address = address; + this.paymentID = paymentID; + this.bitcoin = bitcoin; + this.blockID = 0; + this.poolType = ''; + this.transactionID = 0; + this.sqlID = 0; + if (paymentID === null) { + this.id = address; + } else { + this.id = address + "." + paymentID; + } + this.fee = 0; + this.baseFee = global.support.decimalToCoin(global.config.payout.feeSlewAmount); + this.setFeeAmount = function () { + if (this.amount <= global.support.decimalToCoin(global.config.payout.walletMin)) { + this.fee = this.baseFee; + } else if (this.amount <= global.support.decimalToCoin(global.config.payout.feeSlewEnd)) { + let feeValue = this.baseFee / (global.support.decimalToCoin(global.config.payout.feeSlewEnd) - global.support.decimalToCoin(global.config.payout.walletMin)); + this.fee = this.baseFee - ((this.amount - global.support.decimalToCoin(global.config.payout.walletMin)) * feeValue); + } + this.fee = Math.floor(this.fee); + }; + + this.makePaymentWithID = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn, + payment_id: this.paymentID + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let paymentID = this.paymentID; + let payee = this; + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, address, paymentID, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.makePaymentAsIntegrated = function () { + let paymentDetails = { + destinations: [ + { + amount: this.amount - this.fee, + address: this.address + } + ], + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn + }; + let identifier = this.id; + let amount = this.amount; + let address = this.address; + let payee = this; + + debug("Payment Details: " + JSON.stringify(paymentDetails)); + paymentQueue.push(paymentDetails, function (body) { + if (body.fee && body.fee > 10) { + debug("Successful payment sent to: " + identifier); + global.mysql.query("INSERT INTO transactions (bitcoin, address, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?)", + [0, address, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + }; + + this.trackPayment = function () { + global.mysql.query("UPDATE balance SET amount = amount - ? WHERE id = ?", [this.amount, this.sqlID]); + global.mysql.query("INSERT INTO payments (unlocked_time, paid_time, pool_type, payment_address, transaction_id, bitcoin, amount, payment_id, transfer_fee)" + + " VALUES (now(), now(), ?, ?, ?, ?, ?, ?, ?)", [this.poolType, this.address, this.transactionID, this.bitcoin, this.amount - this.fee, this.paymentID, this.fee]); + }; +} + +function makePayments() { + global.mysql.query("SELECT * FROM balance WHERE amount >= ?", [global.support.decimalToCoin(global.config.payout.walletMin)]).then(function (rows) { + console.log("Loaded all payees into the system for processing"); + let paymentDestinations = []; + let totalAmount = 0; + let roundCount = 0; + let payeeList = []; + let payeeObjects = {}; + rows.forEach(function (row) { + debug("Starting round for: " + JSON.stringify(row)); + let payee = new Payee(row.amount, row.payment_address, row.payment_id, row.bitcoin); + payeeObjects[row.payment_address] = payee; + global.mysql.query("SELECT payout_threshold FROM users WHERE username = ?", [payee.id]).then(function (userRow) { + roundCount += 1; + let threshold = 0; + if (userRow.length !== 0) { + threshold = userRow[0].payout_threshold; + } + payee.poolType = row.pool_type; + payee.sqlID = row.id; + if (payee.poolType === "fees" && payee.address === global.config.payout.feeAddress && payee.amount >= ((global.support.decimalToCoin(global.config.payout.feesForTXN) + global.support.decimalToCoin(global.config.payout.exchangeMin)))) { + debug("This is the fee address internal check for value"); + payee.amount -= global.support.decimalToCoin(global.config.payout.feesForTXN); + } else if (payee.address === global.config.payout.feeAddress && payee.poolType === "fees") { + debug("Unable to pay fee address."); + payee.amount = 0; + } + let remainder = payee.amount % (global.config.payout.denom * global.config.general.sigDivisor); + if (remainder !== 0) { + payee.amount -= remainder; + } + if (payee.amount > threshold) { + payee.setFeeAmount(); + if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length !== 106) { + debug("Adding " + payee.id + " to the list of people to pay (OG Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + paymentDestinations.push({amount: payee.amount - payee.fee, address: payee.address}); + totalAmount += payee.amount; + payeeList.push(payee); + } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 0) { + debug("Adding " + payee.id + " to the list of people to pay (Payment ID Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); + payee.makePaymentWithID(); + } + } + debug("Went: " + roundCount + " With: " + paymentDestinations.length + " Possible destinations and: " + rows.length + " Rows"); + if (roundCount === rows.length && paymentDestinations.length > 0) { + while (paymentDestinations.length > 0) { + let paymentDetails = { + destinations: paymentDestinations.splice(0, global.config.payout.maxPaymentTxns), + priority: global.config.payout.priority, + mixin: global.config.payout.mixIn + }; + console.log("Paying out: " + paymentDetails.destinations.length + " people"); + paymentQueue.push(paymentDetails, function (body) { //jshint ignore:line + // This is the only section that could potentially contain multiple txns. Lets do this safely eh? + if (body.fee && body.fee > 10) { + debug("Made it to the SQL insert for transactions"); + let totalAmount = 0; + paymentDetails.destinations.forEach(function (payeeItem) { + totalAmount += payeeObjects[payeeItem.address].amount; + totalAmount += payeeObjects[payeeItem.address].fee; + }); + global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [0, null, null, totalAmount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, paymentDetails.destinations.length]).then(function (result) { + paymentDetails.destinations.forEach(function (payeeItem) { + payee = payeeObjects[payeeItem.address]; + payee.transactionID = result.insertId; + payee.trackPayment(); + }); + }); + } else { + console.error("Unknown error from the wallet."); + } + }); + } + } + }); + }); + }); +} + +function init() { + global.support.rpcWallet("store", [], function () { + }); + setInterval(function () { + global.support.rpcWallet("store", [], function () { + }); + }, 60000); + console.log("Setting the payment timer to: " + global.config.payout.timer + " minutes with a: " + global.config.payout.timerRetry + " minute delay if the wallet is out of money"); + makePayments(); +} + +init(); From 4c7a4547fba038891e08342afcda9cc6ae795011 Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 11:34:38 -0500 Subject: [PATCH 20/22] Create omb.js --- lib/coins/omb.js | 161 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 lib/coins/omb.js diff --git a/lib/coins/omb.js b/lib/coins/omb.js new file mode 100644 index 00000000..bd3a944f --- /dev/null +++ b/lib/coins/omb.js @@ -0,0 +1,161 @@ +"use strict"; +const bignum = require('bignum'); +const cnUtil = require('cryptonote-util'); +const multiHashing = require('multi-hashing'); +const crypto = require('crypto'); +const debug = require('debug')('coinFuncs'); + +let hexChars = new RegExp("[0-9a-f]+"); + +function Coin(data){ + this.bestExchange = global.config.payout.bestExchange; + this.data = data; + let instanceId = crypto.randomBytes(4); + this.coinDevAddress = "cashBsVQ5Cv9uUHfaMDbgSQ8yryBcAuGCMRjHTuc8e5dYBMcA4hoonFLqLFGZGtaMhX7q6mLJDRiW6zQxYDxdwgD7z6gWqKy1W"; // Developer Address + this.poolDevAddress = "cashPUyMJXpgUaoRA5nSKaMdtvQ1kXwMAXfQbCQiJRUwM54BYLkHKeagCugY7C9CPRfDChH5vHyVk3cfsy2bpm9q4kDyrLD5UR"; // Snipa Address + + this.blockedAddresses = [ + this.coinDevAddress, + this.poolDevAddress + ]; + + this.exchangeAddresses = [ + ]; // These are addresses that MUST have a paymentID to perform logins with. + + this.prefix = 0xe1f54; + this.intPrefix = 0xe9f54; + + if (global.config.general.testnet === true){ + this.prefix = 0xe1f54; + this.intPrefix = 0xe9f54; + } + + this.supportsAutoExchange = false; + + this.niceHashDiff = 200000; + + this.getBlockHeaderByID = function(blockId, callback){ + global.support.rpcDaemon('getblockheaderbyheight', {"height": blockId}, function (body) { + if (body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockHeaderByHash = function(blockHash, callback){ + global.support.rpcDaemon('getblockheaderbyhash', {"hash": blockHash}, function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getLastBlockHeader = function(callback){ + global.support.rpcDaemon('getlastblockheader', [], function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockTemplate = function(walletAddress, callback){ + global.support.rpcDaemon('getblocktemplate', { + reserve_size: 17, + wallet_address: walletAddress + }, function(body){ + return callback(body); + }); + }; + + this.baseDiff = function(){ + return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); + }; + + this.validateAddress = function(address){ + // This function should be able to be called from the async library, as we need to BLOCK ever so slightly to verify the address. + address = new Buffer(address); + if (cnUtil.address_decode(address) === this.prefix){ + return true; + } + return cnUtil.address_decode_integrated(address) === this.intPrefix; + }; + + this.convertBlob = function(blobBuffer){ + return cnUtil.convert_blob(blobBuffer); + }; + + this.constructNewBlob = function(blockTemplate, NonceBuffer){ + return cnUtil.construct_block_blob(blockTemplate, NonceBuffer); + }; + + this.getBlockID = function(blockBuffer){ + return cnUtil.get_block_id(blockBuffer); + }; + + this.BlockTemplate = function(template) { + /* + Generating a block template is a simple thing. Ask for a boatload of information, and go from there. + Important things to consider. + The reserved space is 13 bytes long now in the following format: + Assuming that the extraNonce starts at byte 130: + |130-133|134-137|138-141|142-145| + |minerNonce/extraNonce - 4 bytes|instanceId - 4 bytes|clientPoolNonce - 4 bytes|clientNonce - 4 bytes| + This is designed to allow a single block template to be used on up to 4 billion poolSlaves (clientPoolNonce) + Each with 4 billion clients. (clientNonce) + While being unique to this particular pool thread (instanceId) + With up to 4 billion clients (minerNonce/extraNonce) + Overkill? Sure. But that's what we do here. Overkill. + */ + + // Set this.blob equal to the BT blob that we get from upstream. + this.blob = template.blocktemplate_blob; + this.idHash = crypto.createHash('md5').update(template.blocktemplate_blob).digest('hex'); + // Set this.diff equal to the known diff for this block. + this.difficulty = template.difficulty; + // Set this.height equal to the known height for this block. + this.height = template.height; + // Set this.reserveOffset to the byte location of the reserved offset. + this.reserveOffset = template.reserved_offset; + // Set this.buffer to the binary decoded version of the BT blob. + this.buffer = new Buffer(this.blob, 'hex'); + // Copy the Instance ID to the reserve offset + 4 bytes deeper. Copy in 4 bytes. + instanceId.copy(this.buffer, this.reserveOffset + 4, 0, 3); + // Generate a clean, shiny new buffer. + this.previous_hash = new Buffer(32); + // Copy in bytes 7 through 39 to this.previous_hash from the current BT. + this.buffer.copy(this.previous_hash, 0, 7, 39); + // Reset the Nonce. - This is the per-miner/pool nonce + this.extraNonce = 0; + // The clientNonceLocation is the location at which the client pools should set the nonces for each of their clients. + this.clientNonceLocation = this.reserveOffset + 12; + // The clientPoolLocation is for multi-thread/multi-server pools to handle the nonce for each of their tiers. + this.clientPoolLocation = this.reserveOffset + 8; + this.nextBlob = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Convert the blob into something hashable. + return global.coinFuncs.convertBlob(this.buffer).toString('hex'); + }; + // Make it so you can get the raw block blob out. + this.nextBlobWithChildNonce = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Don't convert the blob to something hashable. You bad. + return this.buffer.toString('hex'); + }; + }; + + this.cryptoNight = multiHashing.cryptonight; + +} + +module.exports = Coin; From 898d7af62d902363a0f4ade7c1ae1506c042e14b Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 11:35:00 -0500 Subject: [PATCH 21/22] Create msr.js --- lib/coins/msr.js | 161 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 lib/coins/msr.js diff --git a/lib/coins/msr.js b/lib/coins/msr.js new file mode 100644 index 00000000..39f9507e --- /dev/null +++ b/lib/coins/msr.js @@ -0,0 +1,161 @@ +"use strict"; +const bignum = require('bignum'); +const cnUtil = require('cryptonote-util'); +const multiHashing = require('multi-hashing'); +const crypto = require('crypto'); +const debug = require('debug')('coinFuncs'); + +let hexChars = new RegExp("[0-9a-f]+"); + +function Coin(data){ + this.bestExchange = global.config.payout.bestExchange; + this.data = data; + let instanceId = crypto.randomBytes(4); + this.coinDevAddress = "cashBsVQ5Cv9uUHfaMDbgSQ8yryBcAuGCMRjHTuc8e5dYBMcA4hoonFLqLFGZGtaMhX7q6mLJDRiW6zQxYDxdwgD7z6gWqKy1W"; // Developer Address + this.poolDevAddress = "cashPUyMJXpgUaoRA5nSKaMdtvQ1kXwMAXfQbCQiJRUwM54BYLkHKeagCugY7C9CPRfDChH5vHyVk3cfsy2bpm9q4kDyrLD5UR"; // Snipa Address + + this.blockedAddresses = [ + this.coinDevAddress, + this.poolDevAddress + ]; + + this.exchangeAddresses = [ + ]; // These are addresses that MUST have a paymentID to perform logins with. + + this.prefix = 28; + this.intPrefix = 29; + + if (global.config.general.testnet === true){ + this.prefix = 33; + this.intPrefix = 34; + } + + this.supportsAutoExchange = false; + + this.niceHashDiff = 200000; + + this.getBlockHeaderByID = function(blockId, callback){ + global.support.rpcDaemon('getblockheaderbyheight', {"height": blockId}, function (body) { + if (body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockHeaderByHash = function(blockHash, callback){ + global.support.rpcDaemon('getblockheaderbyhash', {"hash": blockHash}, function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getLastBlockHeader = function(callback){ + global.support.rpcDaemon('getlastblockheader', [], function (body) { + if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ + return callback(null, body.result.block_header); + } else { + console.error(JSON.stringify(body)); + return callback(true, body); + } + }); + }; + + this.getBlockTemplate = function(walletAddress, callback){ + global.support.rpcDaemon('getblocktemplate', { + reserve_size: 17, + wallet_address: walletAddress + }, function(body){ + return callback(body); + }); + }; + + this.baseDiff = function(){ + return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); + }; + + this.validateAddress = function(address){ + // This function should be able to be called from the async library, as we need to BLOCK ever so slightly to verify the address. + address = new Buffer(address); + if (cnUtil.address_decode(address) === this.prefix){ + return true; + } + return cnUtil.address_decode_integrated(address) === this.intPrefix; + }; + + this.convertBlob = function(blobBuffer){ + return cnUtil.convert_blob(blobBuffer); + }; + + this.constructNewBlob = function(blockTemplate, NonceBuffer){ + return cnUtil.construct_block_blob(blockTemplate, NonceBuffer); + }; + + this.getBlockID = function(blockBuffer){ + return cnUtil.get_block_id(blockBuffer); + }; + + this.BlockTemplate = function(template) { + /* + Generating a block template is a simple thing. Ask for a boatload of information, and go from there. + Important things to consider. + The reserved space is 13 bytes long now in the following format: + Assuming that the extraNonce starts at byte 130: + |130-133|134-137|138-141|142-145| + |minerNonce/extraNonce - 4 bytes|instanceId - 4 bytes|clientPoolNonce - 4 bytes|clientNonce - 4 bytes| + This is designed to allow a single block template to be used on up to 4 billion poolSlaves (clientPoolNonce) + Each with 4 billion clients. (clientNonce) + While being unique to this particular pool thread (instanceId) + With up to 4 billion clients (minerNonce/extraNonce) + Overkill? Sure. But that's what we do here. Overkill. + */ + + // Set this.blob equal to the BT blob that we get from upstream. + this.blob = template.blocktemplate_blob; + this.idHash = crypto.createHash('md5').update(template.blocktemplate_blob).digest('hex'); + // Set this.diff equal to the known diff for this block. + this.difficulty = template.difficulty; + // Set this.height equal to the known height for this block. + this.height = template.height; + // Set this.reserveOffset to the byte location of the reserved offset. + this.reserveOffset = template.reserved_offset; + // Set this.buffer to the binary decoded version of the BT blob. + this.buffer = new Buffer(this.blob, 'hex'); + // Copy the Instance ID to the reserve offset + 4 bytes deeper. Copy in 4 bytes. + instanceId.copy(this.buffer, this.reserveOffset + 4, 0, 3); + // Generate a clean, shiny new buffer. + this.previous_hash = new Buffer(32); + // Copy in bytes 7 through 39 to this.previous_hash from the current BT. + this.buffer.copy(this.previous_hash, 0, 7, 39); + // Reset the Nonce. - This is the per-miner/pool nonce + this.extraNonce = 0; + // The clientNonceLocation is the location at which the client pools should set the nonces for each of their clients. + this.clientNonceLocation = this.reserveOffset + 12; + // The clientPoolLocation is for multi-thread/multi-server pools to handle the nonce for each of their tiers. + this.clientPoolLocation = this.reserveOffset + 8; + this.nextBlob = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Convert the blob into something hashable. + return global.coinFuncs.convertBlob(this.buffer).toString('hex'); + }; + // Make it so you can get the raw block blob out. + this.nextBlobWithChildNonce = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Don't convert the blob to something hashable. You bad. + return this.buffer.toString('hex'); + }; + }; + + this.cryptoNight = multiHashing.cryptonight; + +} + +module.exports = Coin; From 7523aab008e1f1984c00e47f0349f3ae8f48dc8a Mon Sep 17 00:00:00 2001 From: Brian Zalewski Date: Tue, 6 Mar 2018 11:37:12 -0500 Subject: [PATCH 22/22] Update coinConfig.json --- coinConfig.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/coinConfig.json b/coinConfig.json index 80a201ad..8071e700 100644 --- a/coinConfig.json +++ b/coinConfig.json @@ -48,5 +48,21 @@ "name": "Aeon Coin", "mixIn": 4, "shortCode": "AEON" + }, + "omb": { + "funcFile": "./lib/coins/omb.js", + "paymentFile": "./payment_systems/omb.js", + "sigDigits": 1000000000000, + "name": "Ombre", + "mixIn": 12, + "shortCode": "OMB" + }, + "msr": { + "funcFile": "./lib/coins/msr.js", + "paymentFile": "./payment_systems/msr.js", + "sigDigits": 1000000000000, + "name": "Masari", + "mixIn": 12, + "shortCode": "MSR" } }