Adding support for BTC withdrawal

This commit is contained in:
sairajzero 2022-09-30 05:52:30 +05:30
parent d2f1ba8cda
commit bdd79a1a76
6 changed files with 284 additions and 228 deletions

View File

@ -114,19 +114,21 @@ CREATE TABLE BuyOrder (
FOREIGN KEY (asset) REFERENCES AssetList(asset) FOREIGN KEY (asset) REFERENCES AssetList(asset)
); );
CREATE TABLE InputFLO ( CREATE TABLE InputCoin (
id INT NOT NULL AUTO_INCREMENT, id INT NOT NULL AUTO_INCREMENT,
txid VARCHAR(128) NOT NULL, txid VARCHAR(128) NOT NULL,
floID CHAR(34) NOT NULL, floID CHAR(34) NOT NULL,
coin VARCHAR(8) NOT NULL,
amount DECIMAL(16, 8), amount DECIMAL(16, 8),
status VARCHAR(50) NOT NULL, status VARCHAR(50) NOT NULL,
PRIMARY KEY(id) PRIMARY KEY(id)
); );
CREATE TABLE OutputFLO ( CREATE TABLE OutputCoin (
id INT NOT NULL AUTO_INCREMENT, id INT NOT NULL AUTO_INCREMENT,
txid VARCHAR(128), txid VARCHAR(128),
floID CHAR(34) NOT NULL, floID CHAR(34) NOT NULL,
coin VARCHAR(8) NOT NULL,
amount DECIMAL(16, 8) NOT NULL, amount DECIMAL(16, 8) NOT NULL,
status VARCHAR(50) NOT NULL, status VARCHAR(50) NOT NULL,
PRIMARY KEY(id) PRIMARY KEY(id)
@ -146,7 +148,7 @@ CREATE TABLE OutputToken (
id INT NOT NULL AUTO_INCREMENT, id INT NOT NULL AUTO_INCREMENT,
txid VARCHAR(128), txid VARCHAR(128),
floID CHAR(34) NOT NULL, floID CHAR(34) NOT NULL,
token VARCHAR(64), token VARCHAR(64) NOT NULL,
amount DECIMAL(16, 8) NOT NULL, amount DECIMAL(16, 8) NOT NULL,
status VARCHAR(50) NOT NULL, status VARCHAR(50) NOT NULL,
PRIMARY KEY(id) PRIMARY KEY(id)
@ -277,19 +279,19 @@ FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('BuyOrder', NEW.id) ON DUP
CREATE TRIGGER BuyOrder_D AFTER DELETE ON BuyOrder CREATE TRIGGER BuyOrder_D AFTER DELETE ON BuyOrder
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('BuyOrder', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('BuyOrder', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
CREATE TRIGGER InputFLO_I AFTER INSERT ON InputFLO CREATE TRIGGER InputCoin_I AFTER INSERT ON InputCoin
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputCoin', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
CREATE TRIGGER InputFLO_U AFTER UPDATE ON InputFLO CREATE TRIGGER InputCoin_U AFTER UPDATE ON InputCoin
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputCoin', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
CREATE TRIGGER InputFLO_D AFTER DELETE ON InputFLO CREATE TRIGGER InputCoin_D AFTER DELETE ON InputCoin
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputFLO', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputCoin', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
CREATE TRIGGER OutputFLO_I AFTER INSERT ON OutputFLO CREATE TRIGGER OutputCoin_I AFTER INSERT ON OutputCoin
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputCoin', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
CREATE TRIGGER OutputFLO_U AFTER UPDATE ON OutputFLO CREATE TRIGGER OutputCoin_U AFTER UPDATE ON OutputCoin
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputCoin', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
CREATE TRIGGER OutputFLO_D AFTER DELETE ON OutputFLO CREATE TRIGGER OutputCoin_D AFTER DELETE ON OutputCoin
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputFLO', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputCoin', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
CREATE TRIGGER InputToken_I AFTER INSERT ON InputToken CREATE TRIGGER InputToken_I AFTER INSERT ON InputToken
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;

View File

@ -4,9 +4,9 @@ TRUNCATE _backupCache;
TRUNCATE AuditTrade; TRUNCATE AuditTrade;
TRUNCATE BuyOrder; TRUNCATE BuyOrder;
TRUNCATE Distributors; TRUNCATE Distributors;
TRUNCATE InputFLO; TRUNCATE InputCoin;
TRUNCATE InputToken; TRUNCATE InputToken;
TRUNCATE OutputFLO; TRUNCATE OutputCoin;
TRUNCATE OutputToken; TRUNCATE OutputToken;
TRUNCATE PriceHistory; TRUNCATE PriceHistory;
TRUNCATE RequestLog; TRUNCATE RequestLog;

View File

@ -1,4 +1,4 @@
(function(EXPORTS) { //btcOperator v1.0.10 (function(EXPORTS) { //btcOperator v1.0.10a
/* BTC Crypto and API Operator */ /* BTC Crypto and API Operator */
const btcOperator = EXPORTS; const btcOperator = EXPORTS;
@ -31,16 +31,16 @@
}) })
} }
const broadcast = btcOperator.broadcast = rawtx => new Promise((resolve, reject) => { const broadcastTx = btcOperator.broadcastTx = rawTxHex => new Promise((resolve, reject) => {
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: URL + "send_tx/BTC/", url: URL + "send_tx/BTC/",
data: { data: {
"tx_hex": rawtx "tx_hex": rawTxHex
}, },
dataType: "json", dataType: "json",
error: e => reject(e.responseJSON), error: e => reject(e.responseJSON),
success: r => r.status === "success" ? resolve(r.data) : reject(r) success: r => r.status === "success" ? resolve(r.data.txid) : reject(r)
}) })
}); });
@ -485,8 +485,8 @@
new Set(wif_keys).forEach(key => console.debug("Signing key:", key, tx.sign(key, 1 /*sighashtype*/ ))); //Sign the tx using private key WIF new Set(wif_keys).forEach(key => console.debug("Signing key:", key, tx.sign(key, 1 /*sighashtype*/ ))); //Sign the tx using private key WIF
console.debug("Signed:", tx.serialize()); console.debug("Signed:", tx.serialize());
debugger; debugger;
broadcast(tx.serialize()) broadcastTx(tx.serialize())
.then(result => resolve(result)) .then(txid => resolve(txid))
.catch(error => reject(error)); .catch(error => reject(error));
}).catch(error => reject(error)); }).catch(error => reject(error));
}) })

200
src/background.js Normal file
View File

@ -0,0 +1,200 @@
'use strict';
const blockchain = require('./blockchain');
const {
LAUNCH_SELLER_TAG,
MAXIMUM_LAUNCH_SELL_CHIPS,
} = require('./_constants')["market"];
function checkTag(floID, tag) {
return new Promise((resolve, reject) => {
DB.query("SELECT id FROM UserTag WHERE floID=? AND tag=?", [floID, tag])
.then(result => resolve(result.length ? true : false))
.catch(error => reject(error))
})
}
function confirmDepositFLO() {
DB.query("SELECT id, floID, txid FROM InputCoin WHERE coin=? AND status=?", ["FLO", "PENDING"]).then(results => {
results.forEach(req => {
verifyDepositFLO(req.floID, req.txid).then(amount => {
addSellChipsIfLaunchSeller(req.floID, amount).then(txQueries => {
txQueries.push(updateBalance.add(req.floID, "FLO", amount));
txQueries.push(["UPDATE InputCoin SET status=?, amount=? WHERE id=?", ["SUCCESS", amount, req.id]]);
DB.transaction(txQueries)
.then(result => console.debug("FLO deposited:", req.floID, amount))
.catch(error => console.error(error))
}).catch(error => console.error(error))
}).catch(error => {
console.error(error);
if (error[0])
DB.query("UPDATE InputCoin SET status=? WHERE id=?", ["REJECTED", req.id])
.then(_ => null).catch(error => console.error(error));
});
})
}).catch(error => console.error(error))
}
function verifyDepositFLO(sender, txid) {
return new Promise((resolve, reject) => {
floBlockchainAPI.getTx(txid).then(tx => {
let vin_sender = tx.vin.filter(v => v.addr === sender)
if (!vin_sender.length)
return reject([true, "Transaction not sent by the sender"]);
if (vin_sender.length !== tx.vin.length)
return reject([true, "Transaction input containes other floIDs"]);
if (!tx.blockheight)
return reject([false, "Transaction not included in any block yet"]);
if (!tx.confirmations)
return reject([false, "Transaction not confirmed yet"]);
let amount = tx.vout.reduce((a, v) => blockchain.chests.includes(v.scriptPubKey.addresses[0]) ? a + v.value : a, 0);
if (amount == 0)
return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes)
else
resolve(amount);
}).catch(error => reject([false, error]))
})
}
function addSellChipsIfLaunchSeller(floID, quantity) {
return new Promise((resolve, reject) => {
checkTag(floID, LAUNCH_SELLER_TAG).then(result => {
if (result) //floID is launch-seller
Promise.all([
DB.query("SELECT IFNULL(SUM(quantity), 0) AS sold FROM TradeTransactions WHERE seller=? AND asset=?", [floID, 'FLO']),
DB.query("SELECT IFNULL(SUM(quantity), 0) AS brought FROM TradeTransactions WHERE buyer=? AND asset=?", [floID, 'FLO']),
DB.query("SELECT IFNULL(SUM(quantity), 0) AS chips FROM SellChips WHERE floID=? AND asset=?", [floID, 'FLO']),
]).then(result => {
let sold = result[0][0].sold,
brought = result[1][0].brought,
chips = result[2][0].chips;
let remLaunchChips = MAXIMUM_LAUNCH_SELL_CHIPS - (sold + chips) + brought;
quantity = Math.min(quantity, remLaunchChips);
if (quantity > 0)
resolve([
["INSERT INTO SellChips(floID, asset, quantity) VALUES (?, ?, ?)", [floID, 'FLO', quantity]]
]);
else
resolve([]);
}).catch(error => reject(error))
else //floID is not launch-seller
resolve([]);
}).catch(error => reject(error))
})
}
function confirmDepositToken() {
DB.query("SELECT id, floID, txid FROM InputToken WHERE status=?", ["PENDING"]).then(results => {
results.forEach(req => {
verifyDepositToken(req.floID, req.txid).then(amounts => {
DB.query("SELECT id FROM InputCoin where floID=? AND coin=? AND txid=?", [req.floID, "FLO", req.txid]).then(result => {
let txQueries = [],
token_name = amounts[0],
amount_token = amounts[1];
//Add the FLO balance if necessary
if (!result.length) {
let amount_flo = amounts[2];
txQueries.push(updateBalance.add(req.floID, "FLO", amount_flo));
txQueries.push(["INSERT INTO InputCoin(txid, floID, coin, amount, status) VALUES (?, ?, ?, ?, ?)", [req.txid, req.floID, "FLO", amount_flo, "SUCCESS"]]);
}
txQueries.push(["UPDATE InputToken SET status=?, token=?, amount=? WHERE id=?", ["SUCCESS", token_name, amount_token, req.id]]);
txQueries.push(updateBalance.add(req.floID, token_name, amount_token));
DB.transaction(txQueries)
.then(result => console.debug("Token deposited:", req.floID, token_name, amount_token))
.catch(error => console.error(error));
}).catch(error => console.error(error));
}).catch(error => {
console.error(error);
if (error[0])
DB.query("UPDATE InputToken SET status=? WHERE id=?", ["REJECTED", req.id])
.then(_ => null).catch(error => console.error(error));
});
})
}).catch(error => console.error(error))
}
function verifyDepositToken(sender, txid) {
return new Promise((resolve, reject) => {
floTokenAPI.getTx(txid).then(tx => {
if (tx.parsedFloData.type !== "transfer")
return reject([true, "Transaction type not 'transfer'"]);
else if (tx.parsedFloData.transferType !== "token")
return reject([true, "Transaction transfer is not 'token'"]);
var token_name = tx.parsedFloData.tokenIdentification,
amount_token = tx.parsedFloData.tokenAmount;
if ((!assetList.includes(token_name) && token_name !== floGlobals.currency) || token_name === "FLO")
return reject([true, "Token not authorised"]);
let vin_sender = tx.transactionDetails.vin.filter(v => v.addr === sender)
if (!vin_sender.length)
return reject([true, "Transaction not sent by the sender"]);
let amount_flo = tx.transactionDetails.vout.reduce((a, v) => blockchain.chests.includes(v.scriptPubKey.addresses[0]) ? a + v.value : a, 0);
if (amount_flo == 0)
return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes)
else
resolve([token_name, amount_token, amount_flo]);
}).catch(error => reject([false, error]))
})
}
function retryWithdrawalCoin() {
DB.query("SELECT id, floID, coin, amount FROM OutputCoin WHERE status=?", ["PENDING"]).then(results => {
results.forEach(req => blockchain.sendCoin.retry(req.floID, req.coin, req.amount, req.id));
}).catch(error => reject(error));
}
function retryWithdrawalToken() {
DB.query("SELECT id, floID, token, amount FROM OutputToken WHERE status=?", ["PENDING"]).then(results => {
results.forEach(req => blockchain.sendToken.retry(req.floID, req.token, req.amount, req.id));
}).catch(error => reject(error));
}
function confirmWithdrawalFLO() {
DB.query("SELECT id, floID, amount, txid FROM OutputCoin WHERE coin=? AND status=?", ["FLO", "WAITING_CONFIRMATION"]).then(results => {
results.forEach(req => {
floBlockchainAPI.getTx(req.txid).then(tx => {
if (!tx.blockheight || !tx.confirmations) //Still not confirmed
return;
DB.query("UPDATE OutputCoin SET status=? WHERE id=?", ["SUCCESS", req.id])
.then(result => console.debug("FLO withdrawed:", req.floID, req.amount))
.catch(error => console.error(error))
}).catch(error => console.error(error));
})
}).catch(error => console.error(error));
}
function confirmWithdrawalBTC() {
DB.query("SELECT id, floID, amount, txid FROM OutputCoin WHERE coin=? AND status=?", ["BTC", "WAITING_CONFIRMATION"]).then(results => {
results.forEach(req => {
btcOperator.getTx(req.txid).then(tx => {
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
return;
DB.query("UPDATE OutputCoin SET status=? WHERE id=?", ["SUCCESS", req.id])
.then(result => console.debug("BTC withdrawed:", req.floID, req.amount))
.catch(error => console.error(error))
}).catch(error => console.error(error));
})
}).catch(error => console.error(error));
}
function confirmWithdrawalToken() {
DB.query("SELECT id, floID, token, amount, txid FROM OutputToken WHERE status=?", ["WAITING_CONFIRMATION"]).then(results => {
results.forEach(req => {
floTokenAPI.getTx(req.txid).then(tx => {
DB.query("UPDATE OutputToken SET status=? WHERE id=?", ["SUCCESS", req.id])
.then(result => console.debug("Token withdrawed:", req.floID, req.token, req.amount))
.catch(error => console.error(error));
}).catch(error => console.error(error));
})
}).catch(error => console.error(error));
}
module.exports = {
blockchain,
confirmDepositFLO,
confirmDepositToken,
retryWithdrawalCoin,
retryWithdrawalToken,
confirmWithdrawalFLO,
confirmWithdrawalBTC,
confirmWithdrawalToken
}

View File

@ -3,14 +3,28 @@
var collectAndCall; //container for collectAndCall function from backup module var collectAndCall; //container for collectAndCall function from backup module
var chests; //container for blockchain ids (where assets are stored) var chests; //container for blockchain ids (where assets are stored)
const WITHDRAWAL_MESSAGE = "(withdrawal from market)";
const balance_locked = {}, const balance_locked = {},
balance_cache = {}, balance_cache = {},
callbackCollection = { callbackCollection = {
FLO: {}, Coin: {},
token: {} token: {}
}; };
function getSinkID(amount, asset = "FLO", sinkList = null) { function getBalance(sinkID, asset) {
switch (asset) {
case "FLO":
return floBlockchainAPI.getBalance(sinkID);
case "BTC":
let btc_id = btcOperator.convert.legacy2bech(sinkID);
return btcOperator.getBalance(btc_id);
default:
return floTokenAPI.getBalance(sinkID, asset);
}
}
function getSinkID(amount, asset, sinkList = null) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!sinkList) if (!sinkList)
sinkList = chests.list.map(s => [s, s in balance_cache ? balance_cache[s][asset] || 0 : 0]) //TODO: improve sorting sinkList = chests.list.map(s => [s, s in balance_cache ? balance_cache[s][asset] || 0 : 0]) //TODO: improve sorting
@ -18,7 +32,7 @@ function getSinkID(amount, asset = "FLO", sinkList = null) {
if (!sinkList.length) if (!sinkList.length)
return reject(`Insufficient balance in chests for asset(${asset})`); return reject(`Insufficient balance in chests for asset(${asset})`);
let sinkID = sinkList.shift(); let sinkID = sinkList.shift();
(asset === "FLO" ? floBlockchainAPI.getBalance(sinkID) : floTokenAPI.getBalance(sinkID, asset)).then(balance => { getBalance(sinkID, asset).then(balance => {
if (!(sinkID in balance_cache)) if (!(sinkID in balance_cache))
balance_cache[sinkID] = {}; balance_cache[sinkID] = {};
balance_cache[sinkID][asset] = balance; balance_cache[sinkID][asset] = balance;
@ -37,47 +51,55 @@ function getSinkID(amount, asset = "FLO", sinkList = null) {
}) })
} }
function sendFLO(floID, amount, id) { function sendTx(floID, coin, amount, sinkID, sinkKey) {
getSinkID(amount).then(sinkID => { switch (coin) {
case "FLO":
return floBlockchainAPI.sendTx(sinkID, floID, amount, sinkKey, WITHDRAWAL_MESSAGE);
case "BTC":
}
}
function sendCoin(floID, coin, amount, id) {
getSinkID(amount, coin).then(sinkID => {
let callback = (sinkKey) => { let callback = (sinkKey) => {
//Send FLO to user via blockchain API //Send Coin to user via blockchain API
floBlockchainAPI.sendTx(sinkID, floID, amount, sinkKey, '(withdrawal from market)').then(txid => { sendTx(floID, coin, amount, sinkID, sinkKey).then(txid => {
if (!txid) if (!txid)
throw Error("Transaction not successful"); throw Error("Transaction not successful");
//Transaction was successful, Add in DB //Transaction was successful, Add in DB
DB.query("UPDATE OutputFLO SET status=?, txid=? WHERE id=?", ["WAITING_CONFIRMATION", txid, id]) DB.query("UPDATE OutputCoin SET status=?, txid=? WHERE id=?", ["WAITING_CONFIRMATION", txid, id])
.then(_ => null).catch(error => console.error(error)); .then(_ => null).catch(error => console.error(error));
}).catch(error => console.error(error)).finally(_ => { }).catch(error => console.error(error)).finally(_ => {
delete callbackCollection.FLO[id]; delete callbackCollection.Coin[id];
balance_locked[sinkID].FLO -= amount; balance_locked[sinkID][coin] -= amount;
}); });
} }
collectAndCall(sinkID, callback); collectAndCall(sinkID, callback);
callbackCollection.FLO[id] = callback; callbackCollection.Coin[id] = callback;
if (!(sinkID in balance_locked)) if (!(sinkID in balance_locked))
balance_locked[sinkID] = {}; balance_locked[sinkID] = {};
balance_locked[sinkID].FLO = (balance_locked[sinkID].FLO || 0) + amount; balance_locked[sinkID][coin] = (balance_locked[sinkID][coin] || 0) + amount;
}).catch(error => console.error(error)) }).catch(error => console.error(error))
} }
function sendFLO_init(floID, amount) { function sendCoin_init(floID, coin, amount) {
DB.query("INSERT INTO OutputFLO (floID, amount, status) VALUES (?, ?, ?)", [floID, amount, "PENDING"]) DB.query("INSERT INTO OutputCoin (floID, coin, amount, status) VALUES (?, ?, ?, ?)", [floID, coin, amount, "PENDING"])
.then(result => sendFLO(floID, amount, result.insertId)) .then(result => sendCoin(floID, coin, amount, result.insertId))
.catch(error => console.error(error)) .catch(error => console.error(error))
} }
function sendFLO_retry(floID, amount, id) { function sendCoin_retry(floID, coin, amount, id) {
if (id in callbackCollection.FLO) if (id in callbackCollection.Coin)
console.debug("A callback is already pending for this FLO transfer"); console.debug("A callback is already pending for this FLO transfer");
else else
sendFLO(floID, amount, id); sendCoin(floID, coin, amount, id);
} }
function sendToken(floID, token, amount, id) { function sendToken(floID, token, amount, id) {
getSinkID(amount, token).then(sinkID => { getSinkID(amount, token).then(sinkID => {
let callback = (sinkKey) => { let callback = (sinkKey) => {
//Send Token to user via token API //Send Token to user via token API
floTokenAPI.sendToken(sinkKey, amount, floID, '(withdrawal from market)', token).then(txid => { floTokenAPI.sendToken(sinkKey, amount, floID, WITHDRAWAL_MESSAGE, token).then(txid => {
if (!txid) if (!txid)
throw Error("Transaction not successful"); throw Error("Transaction not successful");
//Transaction was successful, Add in DB //Transaction was successful, Add in DB
@ -96,7 +118,7 @@ function sendToken(floID, token, amount, id) {
}).catch(error => console.error(error)) }).catch(error => console.error(error))
} }
function sendToken_init() { function sendToken_init(floID, token, amount) {
DB.query("INSERT INTO OutputToken (floID, token, amount, status) VALUES (?, ?, ?, ?)", [floID, token, amount, "PENDING"]) DB.query("INSERT INTO OutputToken (floID, token, amount, status) VALUES (?, ?, ?, ?)", [floID, token, amount, "PENDING"])
.then(result => sendToken(floID, amount, result.insertId)) .then(result => sendToken(floID, amount, result.insertId))
.catch(error => console.error(error)) .catch(error => console.error(error))
@ -119,9 +141,9 @@ module.exports = {
set chests(c) { set chests(c) {
chests = c; chests = c;
}, },
sendFLO: { sendCoin: {
init: sendFLO_init, init: sendCoin_init,
retry: sendFLO_retry retry: sendCoin_retry
}, },
sendToken: { sendToken: {
init: sendToken_init, init: sendToken_init,

View File

@ -1,13 +1,12 @@
'use strict'; 'use strict';
const coupling = require('./coupling'); const coupling = require('./coupling');
const blockchain = require('./blockchain'); const background = require('./background');
const blockchain = background.blockchain;
const { const {
PERIOD_INTERVAL, PERIOD_INTERVAL,
WAIT_TIME, WAIT_TIME,
LAUNCH_SELLER_TAG,
MAXIMUM_LAUNCH_SELL_CHIPS,
TRADE_HASH_PREFIX, TRADE_HASH_PREFIX,
TRANSFER_HASH_PREFIX TRANSFER_HASH_PREFIX
} = require('./_constants')["market"]; } = require('./_constants')["market"];
@ -258,9 +257,9 @@ function getAccountDetails(floID) {
function getUserTransacts(floID) { function getUserTransacts(floID) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
DB.query("(SELECT 'deposit' as type, txid, token, amount, status FROM InputToken WHERE floID=?)" + DB.query("(SELECT 'deposit' as type, txid, token, amount, status FROM InputToken WHERE floID=?)" +
"UNION (SELECT 'deposit' as type, txid, 'FLO' as token, amount, status FROM InputFLO WHERE floID=?)" + "UNION (SELECT 'deposit' as type, txid, coin as token, amount, status FROM InputCoin WHERE floID=?)" +
"UNION (SELECT 'withdraw' as type, txid, token, amount, status FROM OutputToken WHERE floID=?)" + "UNION (SELECT 'withdraw' as type, txid, token, amount, status FROM OutputToken WHERE floID=?)" +
"UNION (SELECT 'withdraw' as type, txid, 'FLO' as token, amount, status FROM OutputFLO WHERE floID=?)", "UNION (SELECT 'withdraw' as type, txid, coin as token, amount, status FROM OutputCoin WHERE floID=?)",
[floID, floID, floID, floID]) [floID, floID, floID, floID])
.then(result => resolve(result)) .then(result => resolve(result))
.catch(error => reject(error)) .catch(error => reject(error))
@ -339,7 +338,7 @@ function transferToken(sender, receivers, token) {
function depositFLO(floID, txid) { function depositFLO(floID, txid) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
DB.query("SELECT status FROM InputFLO WHERE txid=? AND floID=?", [txid, floID]).then(result => { DB.query("SELECT status FROM InputCoin WHERE txid=? AND floID=? AND coin=?", [txid, floID, "FLO"]).then(result => {
if (result.length) { if (result.length) {
switch (result[0].status) { switch (result[0].status) {
case "PENDING": case "PENDING":
@ -350,82 +349,13 @@ function depositFLO(floID, txid) {
return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already used to add coins")); return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already used to add coins"));
} }
} else } else
DB.query("INSERT INTO InputFLO(txid, floID, status) VALUES (?, ?, ?)", [txid, floID, "PENDING"]) DB.query("INSERT INTO InputCoin(txid, floID, coin, status) VALUES (?, ?, ?, ?)", [txid, floID, "FLO", "PENDING"])
.then(result => resolve("Deposit request in process")) .then(result => resolve("Deposit request in process"))
.catch(error => reject(error)); .catch(error => reject(error));
}).catch(error => reject(error)) }).catch(error => reject(error))
}); });
} }
function confirmDepositFLO() {
DB.query("SELECT id, floID, txid FROM InputFLO WHERE status=?", ["PENDING"]).then(results => {
results.forEach(req => {
confirmDepositFLO.checkTx(req.floID, req.txid).then(amount => {
confirmDepositFLO.addSellChipsIfLaunchSeller(req.floID, amount).then(txQueries => {
txQueries.push(updateBalance.add(req.floID, "FLO", amount));
txQueries.push(["UPDATE InputFLO SET status=?, amount=? WHERE id=?", ["SUCCESS", amount, req.id]]);
DB.transaction(txQueries)
.then(result => console.debug("FLO deposited:", req.floID, amount))
.catch(error => console.error(error))
}).catch(error => console.error(error))
}).catch(error => {
console.error(error);
if (error[0])
DB.query("UPDATE InputFLO SET status=? WHERE id=?", ["REJECTED", req.id])
.then(_ => null).catch(error => console.error(error));
});
})
}).catch(error => console.error(error))
}
confirmDepositFLO.checkTx = function(sender, txid) {
return new Promise((resolve, reject) => {
floBlockchainAPI.getTx(txid).then(tx => {
let vin_sender = tx.vin.filter(v => v.addr === sender)
if (!vin_sender.length)
return reject([true, "Transaction not sent by the sender"]);
if (vin_sender.length !== tx.vin.length)
return reject([true, "Transaction input containes other floIDs"]);
if (!tx.blockheight)
return reject([false, "Transaction not included in any block yet"]);
if (!tx.confirmations)
return reject([false, "Transaction not confirmed yet"]);
let amount = tx.vout.reduce((a, v) => blockchain.chests.includes(v.scriptPubKey.addresses[0]) ? a + v.value : a, 0);
if (amount == 0)
return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes)
else
resolve(amount);
}).catch(error => reject([false, error]))
})
}
confirmDepositFLO.addSellChipsIfLaunchSeller = function(floID, quantity) {
return new Promise((resolve, reject) => {
checkTag(floID, LAUNCH_SELLER_TAG).then(result => {
if (result) //floID is launch-seller
Promise.all([
DB.query("SELECT IFNULL(SUM(quantity), 0) AS sold FROM TradeTransactions WHERE seller=? AND asset=?", [floID, 'FLO']),
DB.query("SELECT IFNULL(SUM(quantity), 0) AS brought FROM TradeTransactions WHERE buyer=? AND asset=?", [floID, 'FLO']),
DB.query("SELECT IFNULL(SUM(quantity), 0) AS chips FROM SellChips WHERE floID=? AND asset=?", [floID, 'FLO']),
]).then(result => {
let sold = result[0][0].sold,
brought = result[1][0].brought,
chips = result[2][0].chips;
let remLaunchChips = MAXIMUM_LAUNCH_SELL_CHIPS - (sold + chips) + brought;
quantity = Math.min(quantity, remLaunchChips);
if (quantity > 0)
resolve([
["INSERT INTO SellChips(floID, asset, quantity) VALUES (?, ?, ?)", [floID, 'FLO', quantity]]
]);
else
resolve([]);
}).catch(error => reject(error))
else //floID is not launch-seller
resolve([]);
}).catch(error => reject(error))
})
}
function withdrawFLO(floID, amount) { function withdrawFLO(floID, amount) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!floCrypto.validateAddr(floID)) if (!floCrypto.validateAddr(floID))
@ -436,33 +366,13 @@ function withdrawFLO(floID, amount) {
let txQueries = []; let txQueries = [];
txQueries.push(updateBalance.consume(floID, "FLO", amount)); txQueries.push(updateBalance.consume(floID, "FLO", amount));
DB.transaction(txQueries).then(result => { DB.transaction(txQueries).then(result => {
blockchain.sendFLO.init(floID, amount); blockchain.sendCoin.init(floID, "FLO", amount);
resolve("Withdrawal request is in process"); resolve("Withdrawal request is in process");
}).catch(error => reject(error)); }).catch(error => reject(error));
}).catch(error => reject(error)); }).catch(error => reject(error));
}); });
} }
function retryWithdrawalFLO() {
DB.query("SELECT id, floID, amount FROM OutputFLO WHERE status=?", ["PENDING"]).then(results => {
results.forEach(req => blockchain.sendFLO.retry(req.floID, req.amount, req.id))
}).catch(error => reject(error));
}
function confirmWithdrawalFLO() {
DB.query("SELECT id, floID, amount, txid FROM OutputFLO WHERE status=?", ["WAITING_CONFIRMATION"]).then(results => {
results.forEach(req => {
floBlockchainAPI.getTx(req.txid).then(tx => {
if (!tx.blockheight || !tx.confirmations) //Still not confirmed
return;
DB.query("UPDATE OutputFLO SET status=? WHERE id=?", ["SUCCESS", req.id])
.then(result => console.debug("FLO withdrawed:", req.floID, req.amount))
.catch(error => console.error(error))
}).catch(error => console.error(error));
})
}).catch(error => console.error(error));
}
function depositToken(floID, txid) { function depositToken(floID, txid) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
DB.query("SELECT status FROM InputToken WHERE txid=? AND floID=?", [txid, floID]).then(result => { DB.query("SELECT status FROM InputToken WHERE txid=? AND floID=?", [txid, floID]).then(result => {
@ -483,59 +393,6 @@ function depositToken(floID, txid) {
}); });
} }
function confirmDepositToken() {
DB.query("SELECT id, floID, txid FROM InputToken WHERE status=?", ["PENDING"]).then(results => {
results.forEach(req => {
confirmDepositToken.checkTx(req.floID, req.txid).then(amounts => {
DB.query("SELECT id FROM InputFLO where floID=? AND txid=?", [req.floID, req.txid]).then(result => {
let txQueries = [],
token_name = amounts[0],
amount_token = amounts[1];
//Add the FLO balance if necessary
if (!result.length) {
let amount_flo = amounts[2];
txQueries.push(updateBalance.add(req.floID, "FLO", amount_flo));
txQueries.push(["INSERT INTO InputFLO(txid, floID, amount, status) VALUES (?, ?, ?, ?)", [req.txid, req.floID, amount_flo, "SUCCESS"]]);
}
txQueries.push(["UPDATE InputToken SET status=?, token=?, amount=? WHERE id=?", ["SUCCESS", token_name, amount_token, req.id]]);
txQueries.push(updateBalance.add(req.floID, token_name, amount_token));
DB.transaction(txQueries)
.then(result => console.debug("Token deposited:", req.floID, token_name, amount_token))
.catch(error => console.error(error));
}).catch(error => console.error(error));
}).catch(error => {
console.error(error);
if (error[0])
DB.query("UPDATE InputToken SET status=? WHERE id=?", ["REJECTED", req.id])
.then(_ => null).catch(error => console.error(error));
});
})
}).catch(error => console.error(error))
}
confirmDepositToken.checkTx = function(sender, txid) {
return new Promise((resolve, reject) => {
floTokenAPI.getTx(txid).then(tx => {
if (tx.parsedFloData.type !== "transfer")
return reject([true, "Transaction type not 'transfer'"]);
else if (tx.parsedFloData.transferType !== "token")
return reject([true, "Transaction transfer is not 'token'"]);
var token_name = tx.parsedFloData.tokenIdentification,
amount_token = tx.parsedFloData.tokenAmount;
if ((!assetList.includes(token_name) && token_name !== floGlobals.currency) || token_name === "FLO")
return reject([true, "Token not authorised"]);
let vin_sender = tx.transactionDetails.vin.filter(v => v.addr === sender)
if (!vin_sender.length)
return reject([true, "Transaction not sent by the sender"]);
let amount_flo = tx.transactionDetails.vout.reduce((a, v) => blockchain.chests.includes(v.scriptPubKey.addresses[0]) ? a + v.value : a, 0);
if (amount_flo == 0)
return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes)
else
resolve([token_name, amount_token, amount_flo]);
}).catch(error => reject([false, error]))
})
}
function withdrawToken(floID, token, amount) { function withdrawToken(floID, token, amount) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!floCrypto.validateAddr(floID)) if (!floCrypto.validateAddr(floID))
@ -561,24 +418,6 @@ function withdrawToken(floID, token, amount) {
}); });
} }
function retryWithdrawalToken() {
DB.query("SELECT id, floID, token, amount FROM OutputToken WHERE status=?", ["PENDING"]).then(results => {
results.forEach(req => blockchain.sendToken.retry(req.floID, req.token, req.amount, req.id));
}).catch(error => reject(error));
}
function confirmWithdrawalToken() {
DB.query("SELECT id, floID, token, amount, txid FROM OutputToken WHERE status=?", ["WAITING_CONFIRMATION"]).then(results => {
results.forEach(req => {
floTokenAPI.getTx(req.txid).then(tx => {
DB.query("UPDATE OutputToken SET status=? WHERE id=?", ["SUCCESS", req.id])
.then(result => console.debug("Token withdrawed:", req.floID, req.token, req.amount))
.catch(error => console.error(error));
}).catch(error => console.error(error));
})
}).catch(error => console.error(error));
}
function addTag(floID, tag) { function addTag(floID, tag) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
DB.query("INSERT INTO UserTag (floID, tag) VALUE (?,?)", [floID, tag]) DB.query("INSERT INTO UserTag (floID, tag) VALUE (?,?)", [floID, tag])
@ -602,14 +441,6 @@ function removeTag(floID, tag) {
}) })
} }
function checkTag(floID, tag) {
return new Promise((resolve, reject) => {
DB.query("SELECT id FROM UserTag WHERE floID=? AND tag=?", [floID, tag])
.then(result => resolve(result.length ? true : false))
.catch(error => reject(error))
})
}
function addDistributor(floID, asset) { function addDistributor(floID, asset) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
DB.query("INSERT INTO Distributors (floID, asset) VALUE (?,?)", [floID, asset]) DB.query("INSERT INTO Distributors (floID, asset) VALUE (?,?)", [floID, asset])
@ -675,12 +506,13 @@ function blockchainReCheck() {
floBlockchainAPI.promisedAPI('api/blocks?limit=1').then(result => { floBlockchainAPI.promisedAPI('api/blocks?limit=1').then(result => {
if (lastSyncBlockHeight < result.blocks[0].height) { if (lastSyncBlockHeight < result.blocks[0].height) {
lastSyncBlockHeight = result.blocks[0].height; lastSyncBlockHeight = result.blocks[0].height;
confirmDepositFLO(); background.confirmDepositFLO();
confirmDepositToken(); background.confirmDepositToken();
retryWithdrawalFLO(); background.retryWithdrawalCoin();
retryWithdrawalToken(); background.retryWithdrawalToken();
confirmWithdrawalFLO(); background.confirmWithdrawalFLO();
confirmWithdrawalToken(); background.confirmWithdrawalBTC();
background.confirmWithdrawalToken();
console.debug("Last Block :", lastSyncBlockHeight); console.debug("Last Block :", lastSyncBlockHeight);
} }
}).catch(error => console.error(error)); }).catch(error => console.error(error));