Implement deploy script, delete redundant coins

This commit is contained in:
poolman 2019-04-23 00:46:26 +03:00
parent 867c55d4da
commit 9f0c0b2a91
17 changed files with 30 additions and 2753 deletions

View file

@ -1,38 +1,4 @@
{
"xmr": {
"funcFile": "./lib/coins/xmr.js",
"paymentFile": "./payment_systems/xmr.js",
"sigDigits": 1000000000000,
"shapeshift": "xmr_btc",
"xmrTo": true,
"name": "Monero",
"mixIn": 4,
"shortCode": "XMR"
},
"krb": {
"funcFile": "./lib/coins/krb.js",
"paymentFile": "./payment_systems/krb.js",
"sigDigits": 1000000000000,
"name": "Karbowanec",
"mixIn": 4,
"shortCode": "KRB"
},
"aeon": {
"funcFile": "./lib/coins/aeon.js",
"paymentFile": "./payment_systems/aeon.js",
"sigDigits": 1000000000000,
"name": "Aeon Coin",
"mixIn": 4,
"shortCode": "AEON"
},
"aeon-rebase": {
"funcFile": "./lib/coins/aeon-rebase.js",
"paymentFile": "./payment_systems/aeon-rebase.js",
"sigDigits": 1000000000000,
"name": "Aeon Coin",
"mixIn": 4,
"shortCode": "AEON"
},
"zano": {
"funcFile": "./lib/coins/zano.js",
"paymentFile": "./payment_systems/zano.js",

View file

@ -3,7 +3,7 @@
"bind_ip": "127.0.0.1",
"hostname": "testpool.com",
"db_storage_path": "CHANGEME",
"coin": "xmr",
"coin": "zano",
"mysql": {
"connectionLimit": 20,
"host": "127.0.0.1",

View file

@ -1,13 +0,0 @@
[Unit]
Description=Aeon Daemon
After=network.target
[Service]
Type=forking
GuessMainPID=no
ExecStart=/usr/local/src/aeon/build/release/bin/aeon --rpc-bind-ip 127.0.0.1 --detach --restricted-rpc
Restart=always
User=aeondaemon
[Install]
WantedBy=multi-user.target

View file

@ -16,7 +16,7 @@ sudo debconf-set-selections <<< "mysql-server mysql-server/root_password_again p
echo -e "[client]\nuser=root\npassword=$ROOT_SQL_PASS" | sudo tee /root/.my.cnf
sudo DEBIAN_FRONTEND=noninteractive apt-get -y install git python-virtualenv python3-virtualenv curl ntp build-essential screen cmake pkg-config libboost-all-dev libevent-dev libunbound-dev libminiupnpc-dev libunwind8-dev liblzma-dev libldns-dev libexpat1-dev libgtest-dev mysql-server lmdb-utils libzmq3-dev
cd ~
git clone https://github.com/Snipa22/nodejs-pool.git # Change this depending on how the deployment goes.
git clone https://github.com/hyle-team/zano-nodejs-pool.git # Change this depending on how the deployment goes.
cd /usr/src/gtest
sudo cmake .
sudo make
@ -24,24 +24,22 @@ sudo mv libg* /usr/lib/
cd ~
sudo systemctl enable ntp
cd /usr/local/src
sudo git clone https://github.com/monero-project/monero.git
cd monero
sudo git checkout v0.11.1.0
curl https://raw.githubusercontent.com/Snipa22/nodejs-pool/master/deployment/monero_daemon.patch | sudo git apply -v
sudo git clone https://github.com/hyle-team/zano.git
cd zano
sudo git checkout master
sudo mkdir build
sudo cd build
sudo cmake ..
sudo make -j$(nproc)
sudo cp ~/nodejs-pool/deployment/monero.service /lib/systemd/system/
sudo useradd -m monerodaemon -d /home/monerodaemon
BLOCKCHAIN_DOWNLOAD_DIR=$(sudo -u monerodaemon mktemp -d)
sudo -u monerodaemon wget --limit-rate=50m -O $BLOCKCHAIN_DOWNLOAD_DIR/blockchain.raw https://downloads.getmonero.org/blockchain.raw
sudo -u monerodaemon /usr/local/src/monero/build/release/bin/monero-blockchain-import --input-file $BLOCKCHAIN_DOWNLOAD_DIR/blockchain.raw --batch-size 20000 --database lmdb#fastest --verify off --data-dir /home/monerodaemon/.bitmonero
sudo -u monerodaemon rm -rf $BLOCKCHAIN_DOWNLOAD_DIR
sudo cp ~/zano-nodejs-pool/deployment/zano.service /lib/systemd/system/
sudo useradd -m zanodaemon -d /home/zanodaemon
sudo systemctl daemon-reload
sudo systemctl enable monero
sudo systemctl start monero
sudo systemctl enable zano
sudo systemctl start zano
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash
source ~/.nvm/nvm.sh
nvm install v8.9.3
cd ~/nodejs-pool
cd ~/zano-nodejs-pool
npm install
npm install -g pm2
openssl req -subj "/C=IT/ST=Pool/L=Daemon/O=Mining Pool/CN=mining.pool" -newkey rsa:2048 -nodes -keyout cert.key -x509 -out cert.pem -days 36500
@ -69,7 +67,7 @@ sudo chown -R root:www-data /etc/caddy
sudo mkdir /etc/ssl/caddy
sudo chown -R www-data:root /etc/ssl/caddy
sudo chmod 0770 /etc/ssl/caddy
sudo cp ~/nodejs-pool/deployment/caddyfile /etc/caddy/Caddyfile
sudo cp ~/zano-nodejs-pool/deployment/caddyfile /etc/caddy/Caddyfile
sudo chown www-data:www-data /etc/caddy/Caddyfile
sudo chmod 444 /etc/caddy/Caddyfile
sudo sh -c "sed 's/ProtectHome=true/ProtectHome=false/' init/linux-systemd/caddy.service > /etc/systemd/system/caddy.service"
@ -81,7 +79,7 @@ sudo systemctl start caddy.service
rm -rf $CADDY_DOWNLOAD_DIR
cd ~
sudo env PATH=$PATH:`pwd`/.nvm/versions/node/v8.9.3/bin `pwd`/.nvm/versions/node/v8.9.3/lib/node_modules/pm2/bin/pm2 startup systemd -u $CURUSER --hp `pwd`
cd ~/nodejs-pool
cd ~/zano-nodejs-pool
sudo chown -R $CURUSER. ~/.pm2
echo "Installing pm2-logrotate in the background!"
pm2 install pm2-logrotate &
@ -89,7 +87,7 @@ mysql -u root --password=$ROOT_SQL_PASS < deployment/base.sql
mysql -u root --password=$ROOT_SQL_PASS pool -e "INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('api', 'authKey', '`cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1`', 'string', 'Auth key sent with all Websocket frames for validation.')"
mysql -u root --password=$ROOT_SQL_PASS pool -e "INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('api', 'secKey', '`cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1`', 'string', 'HMAC key for Passwords. JWT Secret Key. Changing this will invalidate all current logins.')"
pm2 start init.js --name=api --log-date-format="YYYY-MM-DD HH:mm Z" -- --module=api
bash ~/nodejs-pool/deployment/install_lmdb_tools.sh
cd ~/nodejs-pool/sql_sync/
bash ~/zano-nodejs-pool/deployment/install_lmdb_tools.sh
cd ~/zano-nodejs-pool/sql_sync/
env PATH=$PATH:`pwd`/.nvm/versions/node/v8.9.3/bin node sql_sync.js
echo "You're setup! Please read the rest of the readme for the remainder of your setup and configuration. These steps include: Setting your Fee Address, Pool Address, Global Domain, and the Mailgun setup!"

View file

@ -1,96 +0,0 @@
#!/bin/bash
echo "This assumes that you are doing a green-field install. If you're not, please exit in the next 15 seconds."
sleep 15
echo "Continuing install, this will prompt you for your password if you're not already running as root and you didn't enable passwordless sudo. Please do not run me as root!"
if [[ `whoami` == "root" ]]; then
echo "You ran me as root! Do not run me as root!"
exit 1
fi
ROOT_SQL_PASS=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
CURUSER=$(whoami)
echo "Etc/UTC" | sudo tee -a /etc/timezone
sudo rm -rf /etc/localtime
sudo ln -s /usr/share/zoneinfo/Zulu /etc/localtime
sudo dpkg-reconfigure -f noninteractive tzdata
sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get -y upgrade
sudo debconf-set-selections <<< "mysql-server mysql-server/root_password password $ROOT_SQL_PASS"
sudo debconf-set-selections <<< "mysql-server mysql-server/root_password_again password $ROOT_SQL_PASS"
echo -e "[client]\nuser=root\npassword=$ROOT_SQL_PASS" | sudo tee /root/.my.cnf
sudo DEBIAN_FRONTEND=noninteractive apt-get -y install git python-virtualenv python3-virtualenv curl ntp build-essential screen cmake pkg-config libboost-all-dev libevent-dev libunbound-dev libminiupnpc-dev libunwind8-dev liblzma-dev libldns-dev libexpat1-dev libgtest-dev mysql-server lmdb-utils libzmq3-dev
cd /usr/src/gtest
sudo cmake .
sudo make
sudo mv libg* /usr/lib/
cd ~
#sudo systemctl enable ntp
#cd /usr/local/src
#sudo git clone https://github.com/aeonix/aeon.git
#cd aeon
#sudo git checkout v0.9.14.0
#sudo make -j$(nproc)
#sudo cp ~/nodejs-pool/deployment/aeon.service /lib/systemd/system/
#sudo useradd -m aeondaemon -d /home/aeondaemon
#sudo -u aeondaemon mkdir /home/aeondaemon/.aeon
#BLOCKCHAIN_DOWNLOAD_DIR=$(sudo -u aeondaemon mktemp -d)
#sudo -u aeondaemon wget --limit-rate=50m -O $BLOCKCHAIN_DOWNLOAD_DIR/blockchain.bin http://74.208.156.45/blockchain.raw
#sudo -u aeondaemon mv $BLOCKCHAIN_DOWNLOAD_DIR/blockchain.bin /home/aeondaemon/.aeon/blockchain.bin
#sudo -u aeondaemon rm -rf $BLOCKCHAIN_DOWNLOAD_DIR
#sudo systemctl daemon-reload
#sudo systemctl enable aeon
#sudo systemctl start aeon
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash
source ~/.nvm/nvm.sh
nvm install v6.9.2
cd ~/nodejs-pool
npm install
npm install -g pm2
openssl req -subj "/C=IT/ST=Pool/L=Daemon/O=Mining Pool/CN=mining.pool" -newkey rsa:2048 -nodes -keyout cert.key -x509 -out cert.pem -days 36500
mkdir ~/pool_db/
sed -r "s/(\"db_storage_path\": ).*/\1\"\/home\/$CURUSER\/pool_db\/\",/" config_example.json > config.json
cd ~
git clone https://github.com/mesh0000/poolui.git
cd poolui
npm install
./node_modules/bower/bin/bower update
./node_modules/gulp/bin/gulp.js build
cd build
sudo ln -s `pwd` /var/www
CADDY_DOWNLOAD_DIR=$(mktemp -d)
cd $CADDY_DOWNLOAD_DIR
curl -sL "https://snipanet.com/caddy.tar.gz" | tar -xz caddy init/linux-systemd/caddy.service
sudo mv caddy /usr/local/bin
sudo chown root:root /usr/local/bin/caddy
sudo chmod 755 /usr/local/bin/caddy
sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/caddy
sudo groupadd -g 33 www-data
sudo useradd -g www-data --no-user-group --home-dir /var/www --no-create-home --shell /usr/sbin/nologin --system --uid 33 www-data
sudo mkdir /etc/caddy
sudo chown -R root:www-data /etc/caddy
sudo mkdir /etc/ssl/caddy
sudo chown -R www-data:root /etc/ssl/caddy
sudo chmod 0770 /etc/ssl/caddy
sudo cp ~/nodejs-pool/deployment/caddyfile /etc/caddy/Caddyfile
sudo chown www-data:www-data /etc/caddy/Caddyfile
sudo chmod 444 /etc/caddy/Caddyfile
sudo sh -c "sed 's/ProtectHome=true/ProtectHome=false/' init/linux-systemd/caddy.service > /etc/systemd/system/caddy.service"
sudo chown root:root /etc/systemd/system/caddy.service
sudo chmod 644 /etc/systemd/system/caddy.service
sudo systemctl daemon-reload
sudo systemctl enable caddy.service
sudo systemctl start caddy.service
rm -rf $CADDY_DOWNLOAD_DIR
cd ~
sudo env PATH=$PATH:`pwd`/.nvm/versions/node/v6.9.2/bin `pwd`/.nvm/versions/node/v6.9.2/lib/node_modules/pm2/bin/pm2 startup systemd -u $CURUSER --hp `pwd`
cd ~/nodejs-pool
sudo chown -R $CURUSER. ~/.pm2
echo "Installing pm2-logrotate in the background!"
pm2 install pm2-logrotate &
mysql -u root --password=$ROOT_SQL_PASS < deployment/base.sql
mysql -u root --password=$ROOT_SQL_PASS pool -e "INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('api', 'authKey', '`cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1`', 'string', 'Auth key sent with all Websocket frames for validation.')"
mysql -u root --password=$ROOT_SQL_PASS pool -e "INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('api', 'secKey', '`cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1`', 'string', 'HMAC key for Passwords. JWT Secret Key. Changing this will invalidate all current logins.')"
pm2 start init.js --name=api --log-date-format="YYYY-MM-DD HH:mm Z" -- --module=api
bash ~/nodejs-pool/deployment/install_lmdb_tools.sh
cd ~/nodejs-pool/sql_sync/
env PATH=$PATH:`pwd`/.nvm/versions/node/v6.9.2/bin node sql_sync.js
echo "You're setup! Please read the rest of the readme for the remainder of your setup and configuration. These steps include: Setting your Fee Address, Pool Address, Global Domain, and the Mailgun setup!"

View file

@ -1,13 +0,0 @@
[Unit]
Description=Monero Daemon
After=network.target
[Service]
Type=forking
GuessMainPID=no
ExecStart=/usr/local/src/monero/build/release/bin/monerod --rpc-bind-ip 127.0.0.1 --detach --restricted-rpc
Restart=always
User=monerodaemon
[Install]
WantedBy=multi-user.target

View file

@ -1,13 +0,0 @@
diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp
index 4b3fa787..a2340139 100644
--- a/src/cryptonote_core/tx_pool.cpp
+++ b/src/cryptonote_core/tx_pool.cpp
@@ -863,7 +863,7 @@ namespace cryptonote
LockedTXN lock(m_blockchain);
auto sorted_it = m_txs_by_fee_and_receive_time.begin();
- while (sorted_it != m_txs_by_fee_and_receive_time.end())
+ while (sorted_it != m_txs_by_fee_and_receive_time.end() && bl.tx_hashes.size() <= 120)
{
txpool_tx_meta_t meta = m_blockchain.get_txpool_tx_meta(sorted_it->second);
LOG_PRINT_L2("Considering " << sorted_it->second << ", size " << meta.blob_size << ", current block size " << total_size << "/" << max_total_size << ", current coinbase " << print_money(best_coinbase));

View file

@ -1,13 +0,0 @@
#!/bin/bash
echo "This assumes that you have a standard nodejs-pool install, and will patch and update it to the latest stable builds of Monero."
sleep 15
echo "Continuing install, this will prompt you for your password if you didn't enable passwordless sudo. Please do not run me as root!"
cd /usr/local/src/monero
sudo git checkout .
sudo git checkout master
sudo git pull
sudo git checkout origin/release-v0.11.0.0
curl -L https://raw.githubusercontent.com/Snipa22/nodejs-pool/master/deployment/monero_daemon.patch | sudo git apply -v
sudo rm -rf build
sudo make -j$(nproc)
echo "Done building the new Monero daemon! Please go ahead and reboot monero with: sudo systemctl restart monero as soon as the pool source is updated!"

13
deployment/zano.service Normal file
View file

@ -0,0 +1,13 @@
[Unit]
Description=Zano Daemon
After=network.target
[Service]
Type=forking
GuessMainPID=no
ExecStart=/usr/local/src/zano/build/release/src/zanod --rpc-bind-ip 127.0.0.1
Restart=always
User=zanodaemon
[Install]
WantedBy=multi-user.target

View file

@ -1,165 +0,0 @@
"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.isxmr = false;
this.bestExchange = global.config.payout.bestExchange;
this.data = data;
let instanceId = crypto.randomBytes(4);
this.coinDevAddress = "WmsSWgtT1JPg5e3cK41hKXSHVpKW7e47bjgiKmWZkYrhSS5LhRemNyqayaSBtAQ6517eo5PtH9wxHVmM78JDZSUu2W8PqRiNs"; // Developer Address
this.poolDevAddress = "WmtvM6SoYya4qzkoPB4wX7FACWcXyFPWAYzfz7CADECgKyBemAeb3dVb3QomHjRWwGS3VYzMJAnBXfUx5CfGLFZd1U7ssdXTu"; // Snipa Address
this.blockedAddresses = [
this.coinDevAddress,
this.poolDevAddress,
];
this.exchangeAddresses = [
"WmtK9TQ6yd2ZWZDAkRsebc2ppzUq2Wuo9XRRjHMH2fvqM3ARVqk3styJ6AavJFcpJFPFtxRGAqGFoJMZGJ6YYzQ61TYGfpykX", // Bittrex
]; // These are addresses that MUST have a paymentID to perform logins with.
this.prefix = 178;
//this.intPrefix = 0x2733;
if (global.config.general.testnet === true){
this.prefix = 0x0426;
// this.intPrefix = 0x2C27;
}
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 = function(convertedBlob) {
return multiHashing.cryptonight_light(convertedBlob, convertedBlob[0] >= 7 ? convertedBlob[0] - 6 : 0);
}
}
module.exports = Coin;

View file

@ -1,156 +0,0 @@
"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.isxmr = false;
this.bestExchange = global.config.payout.bestExchange;
this.data = data;
let instanceId = crypto.randomBytes(4);
this.coinDevAddress = "WmsSWgtT1JPg5e3cK41hKXSHVpKW7e47bjgiKmWZkYrhSS5LhRemNyqayaSBtAQ6517eo5PtH9wxHVmM78JDZSUu2W8PqRiNs"; // Developer Address
this.poolDevAddress = "WmtvM6SoYya4qzkoPB4wX7FACWcXyFPWAYzfz7CADECgKyBemAeb3dVb3QomHjRWwGS3VYzMJAnBXfUx5CfGLFZd1U7ssdXTu"; // Snipa Address
this.blockedAddresses = [
this.coinDevAddress,
this.poolDevAddress,
];
this.exchangeAddresses = [
]; // These are addresses that MUST have a paymentID to perform logins with.
this.prefix = 178;
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_light;
}
module.exports = Coin;

View file

@ -1,156 +0,0 @@
"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.isxmr = false;
this.bestExchange = global.config.payout.bestExchange;
this.data = data;
let instanceId = crypto.randomBytes(4);
this.coinDevAddress = "Kdev1L9V5ow3cdKNqDpLcFFxZCqu5W2GE9xMKewsB2pUXWxcXvJaUWHcSrHuZw91eYfQFzRtGfTemReSSMN4kE445i6Etb3"; // Developer Address
this.poolDevAddress = "KgseWakG2bMXHGJSsAUfzL1HykCyvD4m8gd9qgcuyZ1ufy8PqUCKRxEfAv3nahfdTrCjZByiWoCiRiohxq4u2rf2RgQ1pcJ"; // Snipa Address
this.blockedAddresses = [
this.coinDevAddress,
this.poolDevAddress,
];
this.exchangeAddresses = [
]; // These are addresses that MUST have a paymentID to perform logins with.
this.prefix = 111;
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;

View file

@ -1,171 +0,0 @@
"use strict";
const bignum = require('bignum');
const cnUtil = require('cryptonote-util');
const multiHashing = require("cryptonight-hashing");
const crypto = require('crypto');
const debug = require('debug')('coinFuncs');
let hexChars = new RegExp("[0-9a-f]+");
function Coin(data){
this.isxmr = true;
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 = function(convertedBlob) {
return multiHashing.cryptonight(convertedBlob, convertedBlob[0] >= 8 ? 8 : 1);
}
}
module.exports = Coin;

View file

@ -1,697 +0,0 @@
"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": <requested_amount_in_btc_as_float>,
"btc_dest_address": "<requested_destination_address_as_string>",
"uuid": "<unique_order_identifier_as_12_character_string>"
}
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) + " AEON with a " + global.support.coinToDecimal(body.result.fee) + " AEON Mining Fee");
return callback(body.result);
} else {
if (transferFunc === 'transfer') {
console.log(body);
console.log("Payment made out to multiple people, total fee: " + global.support.coinToDecimal(body.result.fee) + " AEON");
}
let intCount = 0;
paymentDetails.destinations.forEach(function (details) {
console.log("Payment made to: " + details.address + " For: " + global.support.coinToDecimal(details.amount) + " AEON");
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();

View file

@ -1,257 +0,0 @@
"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();

View file

@ -1,254 +0,0 @@
"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();

View file

@ -1,696 +0,0 @@
"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": <requested_amount_in_btc_as_float>,
"btc_dest_address": "<requested_destination_address_as_string>",
"uuid": "<unique_order_identifier_as_12_character_string>"
}
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" || body.error.message === "not enough unlocked 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();