commit
257995f409
5
.gitignore
vendored
5
.gitignore
vendored
@ -3,4 +3,9 @@
|
||||
/args/config*.json
|
||||
/args/param.json
|
||||
/args/keys*.json
|
||||
/args/prime_index*.b
|
||||
/args/indexes*/
|
||||
/log.txt
|
||||
/bash_start*
|
||||
*test*
|
||||
*.tmp*
|
||||
|
||||
23
README.md
23
README.md
@ -15,24 +15,17 @@ npm run create-schema - Create schema in MySQL database.
|
||||
npm start - Start the application (main).
|
||||
```
|
||||
**NOTE:**
|
||||
env variable `PASSWORD` required for `npm start`.
|
||||
|
||||
Windows:
|
||||
Argument `PASSWORD` required for `npm start`.
|
||||
```
|
||||
$env:PASSWORD="<password>"; npm start
|
||||
```
|
||||
Linux:
|
||||
```
|
||||
PASSWORD="<password"> npm start
|
||||
npm start -- -PASSWORD=<password>
|
||||
```
|
||||
*(Optional)*
|
||||
Multiple instance can be run/setup on the same dir with different config files by using env variable 'I'.
|
||||
|
||||
Windows:
|
||||
Multiple instance can be run/setup on the same dir with different config files by using argument 'I'.
|
||||
```
|
||||
$env:I="<instance_ID>"; <command>
|
||||
<command> -- -I=<instance_ID>
|
||||
```
|
||||
Linux:
|
||||
```
|
||||
I="<instance_ID>" <command>
|
||||
*(Optional)*
|
||||
`console.debug` is now turned off by default. pass argument `--debug` to turn it on
|
||||
```
|
||||
npm start -- -PASSWORD=<password> --debug
|
||||
```
|
||||
429
args/schema.sql
429
args/schema.sql
@ -16,13 +16,12 @@ CREATE TABLE TagList (
|
||||
tag VARCHAR(50) NOT NULL,
|
||||
sellPriority INT,
|
||||
buyPriority INT,
|
||||
api TINYTEXT,
|
||||
PRIMARY KEY(tag)
|
||||
);
|
||||
|
||||
CREATE TABLE AssetList (
|
||||
asset VARCHAR(64) NOT NULL,
|
||||
initialPrice FLOAT,
|
||||
initialPrice DECIMAL(16, 8),
|
||||
PRIMARY KEY(asset)
|
||||
);
|
||||
|
||||
@ -37,26 +36,27 @@ CREATE TABLE UserSession (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
floID CHAR(34) NOT NULL,
|
||||
proxyKey CHAR(66) NOT NULL,
|
||||
session_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
session_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
KEY (id),
|
||||
PRIMARY KEY(floID)
|
||||
);
|
||||
|
||||
CREATE TABLE Cash (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
floID CHAR(34) NOT NULL UNIQUE,
|
||||
balance DECIMAL(12, 2) DEFAULT 0.00,
|
||||
KEY(id),
|
||||
PRIMARY KEY(floID)
|
||||
);
|
||||
|
||||
CREATE TABLE Vault (
|
||||
CREATE TABLE UserBalance (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
floID CHAR(34) NOT NULL,
|
||||
locktime DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
token VARCHAR(64) NOT NULL,
|
||||
quantity DECIMAL(16, 8) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(floID, token),
|
||||
KEY(id)
|
||||
);
|
||||
|
||||
CREATE TABLE SellChips (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
floID CHAR(34) NOT NULL,
|
||||
locktime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
asset VARCHAR(64) NOT NULL,
|
||||
base DECIMAL(10, 2),
|
||||
quantity FLOAT NOT NULL,
|
||||
base DECIMAL(16, 8) NOT NULL DEFAULT 0,
|
||||
quantity DECIMAL(16, 8) NOT NULL,
|
||||
PRIMARY KEY(id),
|
||||
FOREIGN KEY (asset) REFERENCES AssetList(asset)
|
||||
);
|
||||
@ -70,6 +70,15 @@ CREATE TABLE UserTag (
|
||||
FOREIGN KEY (tag) REFERENCES TagList(tag)
|
||||
);
|
||||
|
||||
CREATE TABLE Distributors(
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
floID CHAR(34) NOT NULL,
|
||||
asset VARCHAR(64) NOT NULL,
|
||||
KEY(id),
|
||||
PRIMARY KEY(floID, asset),
|
||||
FOREIGN KEY (asset) REFERENCES AssetList(asset)
|
||||
);
|
||||
|
||||
/* User Requests */
|
||||
|
||||
CREATE TABLE RequestLog(
|
||||
@ -78,7 +87,7 @@ CREATE TABLE RequestLog(
|
||||
request TEXT NOT NULL,
|
||||
sign VARCHAR(160) NOT NULL,
|
||||
proxy BOOLEAN NOT NULL,
|
||||
request_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
request_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE (sign)
|
||||
);
|
||||
@ -87,9 +96,9 @@ CREATE TABLE SellOrder (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
floID CHAR(34) NOT NULL,
|
||||
asset VARCHAR(64) NOT NULL,
|
||||
quantity FLOAT NOT NULL,
|
||||
minPrice DECIMAL(10, 2) NOT NULL,
|
||||
time_placed DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
quantity DECIMAL(16, 8) NOT NULL,
|
||||
minPrice DECIMAL(16, 8) NOT NULL,
|
||||
time_placed TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY(id),
|
||||
FOREIGN KEY (asset) REFERENCES AssetList(asset)
|
||||
);
|
||||
@ -98,48 +107,23 @@ CREATE TABLE BuyOrder (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
floID CHAR(34) NOT NULL,
|
||||
asset VARCHAR(64) NOT NULL,
|
||||
quantity FLOAT NOT NULL,
|
||||
maxPrice DECIMAL(10, 2) NOT NULL,
|
||||
time_placed DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
quantity DECIMAL(16, 8) NOT NULL,
|
||||
maxPrice DECIMAL(16, 8) NOT NULL,
|
||||
time_placed TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY(id),
|
||||
FOREIGN KEY (asset) REFERENCES AssetList(asset)
|
||||
);
|
||||
|
||||
CREATE TABLE InputFLO (
|
||||
CREATE TABLE VaultTransactions (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
txid VARCHAR(128) NOT NULL,
|
||||
floID CHAR(34) NOT NULL,
|
||||
amount FLOAT,
|
||||
status VARCHAR(50) NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
);
|
||||
|
||||
CREATE TABLE OutputFLO (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
mode TINYINT NOT NULL,
|
||||
asset_type TINYINT NOT NULL,
|
||||
asset VARCHAR(32),
|
||||
amount DECIMAL(16, 8),
|
||||
txid VARCHAR(128),
|
||||
floID CHAR(34) NOT NULL,
|
||||
amount FLOAT NOT NULL,
|
||||
status VARCHAR(50) NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
);
|
||||
|
||||
CREATE TABLE InputToken (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
txid VARCHAR(128) NOT NULL,
|
||||
floID CHAR(34) NOT NULL,
|
||||
token VARCHAR(64),
|
||||
amount FLOAT,
|
||||
status VARCHAR(50) NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
);
|
||||
|
||||
CREATE TABLE OutputToken (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
txid VARCHAR(128),
|
||||
floID CHAR(34) NOT NULL,
|
||||
token VARCHAR(64),
|
||||
amount FLOAT NOT NULL,
|
||||
status VARCHAR(50) NOT NULL,
|
||||
locktime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
r_status TINYINT NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
);
|
||||
|
||||
@ -148,8 +132,8 @@ CREATE TABLE OutputToken (
|
||||
CREATE TABLE PriceHistory (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
asset VARCHAR(64) NOT NULL,
|
||||
rate FLOAT NOT NULL,
|
||||
rec_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
rate DECIMAL(16, 8) NOT NULL,
|
||||
rec_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY(id),
|
||||
FOREIGN KEY (asset) REFERENCES AssetList(asset)
|
||||
);
|
||||
@ -159,8 +143,8 @@ CREATE TABLE TransferTransactions (
|
||||
sender CHAR(34) NOT NULL,
|
||||
receiver TEXT NOT NULL,
|
||||
token VARCHAR(64) NOT NULL,
|
||||
totalAmount FLOAT NOT NULL,
|
||||
tx_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
totalAmount DECIMAL(16, 8) NOT NULL,
|
||||
tx_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
txid VARCHAR(66) NOT NULL,
|
||||
KEY(id),
|
||||
PRIMARY KEY(txid)
|
||||
@ -171,9 +155,9 @@ CREATE TABLE TradeTransactions (
|
||||
seller CHAR(34) NOT NULL,
|
||||
buyer CHAR(34) NOT NULL,
|
||||
asset VARCHAR(64) NOT NULL,
|
||||
quantity FLOAT NOT NULL,
|
||||
unitValue DECIMAL(10, 2) NOT NULL,
|
||||
tx_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
quantity DECIMAL(16, 8) NOT NULL,
|
||||
unitValue DECIMAL(16, 8) NOT NULL,
|
||||
tx_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
txid VARCHAR(66) NOT NULL,
|
||||
KEY(id),
|
||||
PRIMARY KEY(txid),
|
||||
@ -182,151 +166,300 @@ CREATE TABLE TradeTransactions (
|
||||
|
||||
CREATE TABLE AuditTrade(
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
rec_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
unit_price FLOAT NOT NULL,
|
||||
quantity FLOAT NOT NULL,
|
||||
total_cost FLOAT NOT NULL,
|
||||
rec_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
unit_price DECIMAL(16, 8) NOT NULL,
|
||||
quantity DECIMAL(16, 8) NOT NULL,
|
||||
total_cost DECIMAL(16, 8) NOT NULL,
|
||||
asset VARCHAR(64) NOT NULL,
|
||||
sellerID CHAR(34) NOT NULL,
|
||||
seller_old_asset FLOAT NOT NULL,
|
||||
seller_new_asset FLOAT NOT NULL,
|
||||
seller_old_cash FLOAT NOT NULL,
|
||||
seller_new_cash FLOAT NOT NULL,
|
||||
seller_old_asset DECIMAL(16, 8) NOT NULL,
|
||||
seller_new_asset DECIMAL(16, 8) NOT NULL,
|
||||
seller_old_cash DECIMAL(16, 8) NOT NULL,
|
||||
seller_new_cash DECIMAL(16, 8) NOT NULL,
|
||||
buyerID CHAR(34) NOT NULL,
|
||||
buyer_old_asset FLOAT NOT NULL,
|
||||
buyer_new_asset FLOAT NOT NULL,
|
||||
buyer_old_cash FLOAT NOT NULL,
|
||||
buyer_new_cash FLOAT NOT NULL,
|
||||
buyer_old_asset DECIMAL(16, 8) NOT NULL,
|
||||
buyer_new_asset DECIMAL(16, 8) NOT NULL,
|
||||
buyer_old_cash DECIMAL(16, 8) NOT NULL,
|
||||
buyer_new_cash DECIMAL(16, 8) NOT NULL,
|
||||
PRIMARY KEY(id),
|
||||
FOREIGN KEY (asset) REFERENCES AssetList(asset)
|
||||
);
|
||||
|
||||
/* External Service */
|
||||
|
||||
CREATE TABLE BlockchainBonds(
|
||||
bond_id VARCHAR(128) NOT NULL,
|
||||
floID CHAR(34) NOT NULL,
|
||||
amount_in DECIMAL(16, 2) NOT NULL,
|
||||
begin_date DATE NOT NULL,
|
||||
btc_base DECIMAL(16, 2) NOT NULL,
|
||||
usd_base DECIMAL(16, 2) NOT NULL,
|
||||
gain_cut DECIMAL(6, 5) NOT NULL,
|
||||
min_ipa DECIMAL(6, 5) NOT NULL,
|
||||
max_period VARCHAR(10) NOT NULL,
|
||||
lockin_period VARCHAR(10) NOT NULL,
|
||||
close_id VARCHAR(128),
|
||||
amount_out DECIMAL(16, 2),
|
||||
PRIMARY KEY(bond_id)
|
||||
);
|
||||
|
||||
CREATE TABLE CloseBondTransact(
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
bond_id VARCHAR(128) NOT NULL,
|
||||
floID CHAR(34) NOT NULL,
|
||||
amount DECIMAL(16, 2) NOT NULL,
|
||||
end_date DATE NOT NULL,
|
||||
ref_sign VARCHAR(180) NOT NULL,
|
||||
btc_net DECIMAL(16, 2) NOT NULL,
|
||||
usd_net DECIMAL(16, 2) NOT NULL,
|
||||
txid VARCHAR(128),
|
||||
close_id VARCHAR(128),
|
||||
r_status TINYINT NOT NULL,
|
||||
KEY(id),
|
||||
PRIMARY KEY(bond_id),
|
||||
FOREIGN KEY (bond_id) REFERENCES BlockchainBonds(bond_id)
|
||||
);
|
||||
|
||||
CREATE TABLE BobsFund(
|
||||
fund_id VARCHAR(128) NOT NULL,
|
||||
begin_date DATE NOT NULL,
|
||||
btc_base DECIMAL(16, 2) NOT NULL,
|
||||
usd_base DECIMAL(16, 2) NOT NULL,
|
||||
fee DECIMAL(6, 5) NOT NULL,
|
||||
duration VARCHAR(10) NOT NULL,
|
||||
tapout_window VARCHAR(10),
|
||||
tapout_interval VARCHAR(40),
|
||||
PRIMARY KEY(fund_id)
|
||||
);
|
||||
|
||||
CREATE TABLE BobsFundInvestments(
|
||||
fund_id VARCHAR(128) NOT NULL,
|
||||
floID CHAR(34) NOT NULL,
|
||||
amount_in DECIMAL(16, 2) NOT NULL,
|
||||
close_id VARCHAR(128),
|
||||
amount_out DECIMAL(16, 2),
|
||||
PRIMARY KEY(fund_id, floID),
|
||||
FOREIGN KEY (fund_id) REFERENCES BobsFund(fund_id)
|
||||
);
|
||||
|
||||
CREATE TABLE CloseFundTransact(
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
fund_id VARCHAR(128) NOT NULL,
|
||||
floID CHAR(34) NOT NULL,
|
||||
amount DECIMAL(16, 2) NOT NULL,
|
||||
end_date DATE NOT NULL,
|
||||
ref_sign VARCHAR(180) NOT NULL,
|
||||
btc_net DECIMAL(16, 2) NOT NULL,
|
||||
usd_net DECIMAL(16, 2) NOT NULL,
|
||||
txid VARCHAR(128),
|
||||
close_id VARCHAR(128),
|
||||
r_status TINYINT NOT NULL,
|
||||
KEY(id),
|
||||
PRIMARY KEY(fund_id, floID),
|
||||
FOREIGN KEY (fund_id) REFERENCES BobsFund(fund_id)
|
||||
);
|
||||
|
||||
CREATE TABLE ConvertFund(
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
amount DECIMAL(16, 8),
|
||||
coin VARCHAR(8) NOT NULL,
|
||||
quantity DECIMAL(16, 8),
|
||||
mode TINYINT NOT NULL,
|
||||
txid VARCHAR(128),
|
||||
r_status TINYINT NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
);
|
||||
|
||||
CREATE TABLE DirectConvert(
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
floID CHAR(34) NOT NULL,
|
||||
amount DECIMAL(16, 8),
|
||||
coin VARCHAR(8) NOT NULL,
|
||||
quantity DECIMAL(16, 8),
|
||||
mode TINYINT NOT NULL,
|
||||
rate DECIMAL(16, 2),
|
||||
in_txid VARCHAR(128),
|
||||
out_txid VARCHAR(128),
|
||||
locktime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
r_status TINYINT NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
);
|
||||
|
||||
CREATE TABLE RefundConvert(
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
floID CHAR(34) NOT NULL,
|
||||
asset_type TINYINT NOT NULL,
|
||||
asset VARCHAR(32) NOT NULL,
|
||||
amount DECIMAL(16, 8),
|
||||
in_txid VARCHAR(128),
|
||||
out_txid VARCHAR(128),
|
||||
locktime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
r_status TINYINT NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
);
|
||||
|
||||
/* Backup Feature (Tables & Triggers) */
|
||||
|
||||
CREATE TABLE _backup (
|
||||
t_name VARCHAR(20),
|
||||
t_name VARCHAR(64),
|
||||
id INT,
|
||||
mode BOOLEAN DEFAULT TRUE,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
u_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY(t_name, id)
|
||||
);
|
||||
|
||||
CREATE table _backupCache(
|
||||
id INT AUTO_INCREMENT,
|
||||
t_name TINYTEXT,
|
||||
t_name VARCHAR(64),
|
||||
data_cache LONGTEXT,
|
||||
status BOOLEAN,
|
||||
fail BOOLEAN,
|
||||
PRIMARY KEY(id)
|
||||
);
|
||||
|
||||
CREATE TABLE sinkShares(
|
||||
floID CHAR(34) NOT NULL,
|
||||
num INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
share TEXT,
|
||||
time_ DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
time_stored TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY(num)
|
||||
);
|
||||
|
||||
CREATE TABLE discardedSinks(
|
||||
id INT AUTO_INCREMENT,
|
||||
floID CHAR(34) NOT NULL,
|
||||
discard_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
KEY(id),
|
||||
PRIMARY KEY(floID)
|
||||
);
|
||||
|
||||
CREATE TRIGGER discardedSinks_I AFTER INSERT ON discardedSinks
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('discardedSinks', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER discardedSinks_U AFTER UPDATE ON discardedSinks
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('discardedSinks', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER discardedSinks_D AFTER DELETE ON discardedSinks
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('discardedSinks', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER RequestLog_I AFTER INSERT ON RequestLog
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RequestLog', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RequestLog', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER RequestLog_U AFTER UPDATE ON RequestLog
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RequestLog', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RequestLog', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER RequestLog_D AFTER DELETE ON RequestLog
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RequestLog', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RequestLog', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER UserSession_I AFTER INSERT ON UserSession
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserSession', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserSession', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER UserSession_U AFTER UPDATE ON UserSession
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserSession', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserSession', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER UserSession_D AFTER DELETE ON UserSession
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserSession', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserSession', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER Cash_I AFTER INSERT ON Cash
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Cash', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER Cash_U AFTER UPDATE ON Cash
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Cash', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER Cash_D AFTER DELETE ON Cash
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Cash', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
|
||||
CREATE TRIGGER UserBalance_I AFTER INSERT ON UserBalance
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserBalance', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER UserBalance_U AFTER UPDATE ON UserBalance
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserBalance', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER UserBalance_D AFTER DELETE ON UserBalance
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserBalance', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER Vault_I AFTER INSERT ON Vault
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Vault', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER Vault_U AFTER UPDATE ON Vault
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Vault', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER Vault_D AFTER DELETE ON Vault
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Vault', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
|
||||
CREATE TRIGGER SellChips_I AFTER INSERT ON SellChips
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('SellChips', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER SellChips_U AFTER UPDATE ON SellChips
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('SellChips', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER SellChips_D AFTER DELETE ON SellChips
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('SellChips', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER SellOrder_I AFTER INSERT ON SellOrder
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('SellOrder', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('SellOrder', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER SellOrder_U AFTER UPDATE ON SellOrder
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('SellOrder', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('SellOrder', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER SellOrder_D AFTER DELETE ON SellOrder
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('SellOrder', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('SellOrder', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER BuyOrder_I AFTER INSERT ON BuyOrder
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('BuyOrder', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('BuyOrder', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER BuyOrder_U AFTER UPDATE ON BuyOrder
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('BuyOrder', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('BuyOrder', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
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, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER InputFLO_I AFTER INSERT ON InputFLO
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER InputFLO_U AFTER UPDATE ON InputFLO
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER InputFLO_D AFTER DELETE ON InputFLO
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputFLO', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
|
||||
CREATE TRIGGER VaultTransactions_I AFTER INSERT ON VaultTransactions
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('VaultTransactions', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER VaultTransactions_U AFTER UPDATE ON VaultTransactions
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('VaultTransactions', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER VaultTransactions_D AFTER DELETE ON VaultTransactions
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('VaultTransactions', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER OutputFLO_I AFTER INSERT ON OutputFLO
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER OutputFLO_U AFTER UPDATE ON OutputFLO
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER OutputFLO_D AFTER DELETE ON OutputFLO
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputFLO', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
|
||||
CREATE TRIGGER CloseBondTransact_I AFTER INSERT ON CloseBondTransact
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('CloseBondTransact', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER CloseBondTransact_U AFTER UPDATE ON CloseBondTransact
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('CloseBondTransact', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER CloseBondTransact_D AFTER DELETE ON CloseBondTransact
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('CloseBondTransact', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
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;
|
||||
CREATE TRIGGER InputToken_U AFTER UPDATE ON InputToken
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER InputToken_D AFTER DELETE ON InputToken
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputToken', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
|
||||
CREATE TRIGGER CloseFundTransact_I AFTER INSERT ON CloseFundTransact
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('CloseFundTransact', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER CloseFundTransact_U AFTER UPDATE ON CloseFundTransact
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('CloseFundTransact', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER CloseFundTransact_D AFTER DELETE ON CloseFundTransact
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('CloseFundTransact', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER OutputToken_I AFTER INSERT ON OutputToken
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER OutputToken_U AFTER UPDATE ON OutputToken
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER OutputToken_D AFTER DELETE ON OutputToken
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputToken', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
|
||||
CREATE TRIGGER ConvertFund_I AFTER INSERT ON ConvertFund
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('ConvertFund', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER ConvertFund_U AFTER UPDATE ON ConvertFund
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('ConvertFund', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER ConvertFund_D AFTER DELETE ON ConvertFund
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('ConvertFund', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER DirectConvert_I AFTER INSERT ON DirectConvert
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('DirectConvert', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER DirectConvert_U AFTER UPDATE ON DirectConvert
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('DirectConvert', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER DirectConvert_D AFTER DELETE ON DirectConvert
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('DirectConvert', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER RefundConvert_I AFTER INSERT ON RefundConvert
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RefundConvert', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER RefundConvert_U AFTER UPDATE ON RefundConvert
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RefundConvert', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER RefundConvert_D AFTER DELETE ON RefundConvert
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('RefundConvert', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER UserTag_I AFTER INSERT ON UserTag
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserTag', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserTag', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER UserTag_U AFTER UPDATE ON UserTag
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserTag', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserTag', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER UserTag_D AFTER DELETE ON UserTag
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserTag', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserTag', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER Distributors_I AFTER INSERT ON Distributors
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Distributors', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER Distributors_U AFTER UPDATE ON Distributors
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Distributors', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER Distributors_D AFTER DELETE ON Distributors
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Distributors', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER PriceHistory_I AFTER INSERT ON PriceHistory
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('PriceHistory', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('PriceHistory', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER PriceHistory_U AFTER UPDATE ON PriceHistory
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('PriceHistory', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('PriceHistory', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER PriceHistory_D AFTER DELETE ON PriceHistory
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('PriceHistory', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('PriceHistory', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER AuditTransaction_I AFTER INSERT ON AuditTrade
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('AuditTrade', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER AuditTransaction_U AFTER UPDATE ON AuditTrade
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('AuditTrade', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER AuditTransaction_D AFTER DELETE ON AuditTrade
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('AuditTrade', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
|
||||
CREATE TRIGGER AuditTrade_I AFTER INSERT ON AuditTrade
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('AuditTrade', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER AuditTrade_U AFTER UPDATE ON AuditTrade
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('AuditTrade', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER AuditTrade_D AFTER DELETE ON AuditTrade
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('AuditTrade', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER TransactionHistory_I AFTER INSERT ON TradeTransactions
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('TradeTransactions', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER TransactionHistory_U AFTER UPDATE ON TradeTransactions
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('TradeTransactions', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER TransactionHistory_D AFTER DELETE ON TradeTransactions
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('TradeTransactions', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
|
||||
CREATE TRIGGER TradeTransactions_I AFTER INSERT ON TradeTransactions
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('TradeTransactions', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER TradeTransactions_U AFTER UPDATE ON TradeTransactions
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('TradeTransactions', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER TradeTransactions_D AFTER DELETE ON TradeTransactions
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('TradeTransactions', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
|
||||
CREATE TRIGGER TransferTransactions_I AFTER INSERT ON TransferTransactions
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('TransferTransactions', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('TransferTransactions', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER TransferTransactions_U AFTER UPDATE ON TransferTransactions
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('TransferTransactions', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('TransferTransactions', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=DEFAULT;
|
||||
CREATE TRIGGER TransferTransactions_D AFTER DELETE ON TransferTransactions
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('TransferTransactions', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('TransferTransactions', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, u_time=DEFAULT;
|
||||
@ -1,24 +1,31 @@
|
||||
/* Node data */
|
||||
TRUNCATE _backup;
|
||||
TRUNCATE _backupCache;
|
||||
TRUNCATE AuditTrade;
|
||||
TRUNCATE BuyOrder;
|
||||
TRUNCATE Cash;
|
||||
TRUNCATE InputFLO;
|
||||
TRUNCATE InputToken;
|
||||
TRUNCATE OutputFLO;
|
||||
TRUNCATE OutputToken;
|
||||
TRUNCATE Distributors;
|
||||
TRUNCATE VaultTransactions;
|
||||
TRUNCATE PriceHistory;
|
||||
TRUNCATE RequestLog;
|
||||
TRUNCATE SellOrder;
|
||||
TRUNCATE UserBalance;
|
||||
TRUNCATE UserSession;
|
||||
TRUNCATE UserTag;
|
||||
TRUNCATE TransferTransactions;
|
||||
TRUNCATE TradeTransactions;
|
||||
TRUNCATE Vault;
|
||||
TRUNCATE SellChips;
|
||||
TRUNCATE CloseBondTransact;
|
||||
TRUNCATE CloseFundTransact;
|
||||
TRUNCATE ConvertFund;
|
||||
TRUNCATE DirectConvert;
|
||||
TRUNCATE RefundConvert;
|
||||
|
||||
/* Blockchain data */
|
||||
TRUNCATE LastTx;
|
||||
TRUNCATE NodeList;
|
||||
TRUNCATE TrustedList;
|
||||
DELETE FROM BlockchainBonds;
|
||||
TRUNCATE BobsFundInvestments;
|
||||
DELETE FROM BobsFund;
|
||||
DELETE FROM TagList;
|
||||
DELETE FROM AssetList;
|
||||
1
docs/css/back.svg
Normal file
1
docs/css/back.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080"><defs><style>.a{fill:#ffcdab;}.b{fill:#fff4c5;}</style></defs><title>back</title><path class="a" d="M1343.5-.5c33.54,48.75,50.34,166,277.18,193.18C1808.39,215.21,1899.21,313.38,1920,364c-.33-146.23-.17-218.31-.5-364.54Z"/><path class="b" d="M1343.5-.5a288.26,288.26,0,0,0,159,88.78c48.38,9.32,76.65,13.77,132.58,22.68,93.5,14.9,141.37,56.9,170.42,80.54a466.36,466.36,0,0,1,115,139q-.49-165.49-1-331Z"/></svg>
|
||||
|
After Width: | Height: | Size: 499 B |
33
docs/css/bg-art2.svg
Normal file
33
docs/css/bg-art2.svg
Normal file
@ -0,0 +1,33 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<g filter="url(#filter0_d)">
|
||||
<rect x="312" y="323" width="240" height="140" rx="18" transform="rotate(30 312 323)" fill="#1C783B"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_d)">
|
||||
<rect x="431.283" y="302" width="240" height="140" rx="18" transform="rotate(50.0235 431.283 302)" fill="#2DBD5E"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d" x="225" y="320" width="301.846" height="265.244" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx="-5" dy="9"/>
|
||||
<feGaussianBlur stdDeviation="6"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_d" x="307" y="299" width="285.477" height="297.86" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx="-5" dy="9"/>
|
||||
<feGaussianBlur stdDeviation="6"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0">
|
||||
<rect width="512" height="512" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
1224
docs/css/main.css
1224
docs/css/main.css
File diff suppressed because it is too large
Load Diff
2
docs/css/main.min.css
vendored
2
docs/css/main.min.css
vendored
File diff suppressed because one or more lines are too long
1186
docs/css/main.scss
1186
docs/css/main.scss
File diff suppressed because it is too large
Load Diff
2719
docs/index.html
2719
docs/index.html
File diff suppressed because one or more lines are too long
734
docs/scripts/btcOperator.js
Normal file
734
docs/scripts/btcOperator.js
Normal file
@ -0,0 +1,734 @@
|
||||
(function (EXPORTS) { //btcOperator v1.0.13c
|
||||
/* BTC Crypto and API Operator */
|
||||
const btcOperator = EXPORTS;
|
||||
|
||||
//This library uses API provided by chain.so (https://chain.so/)
|
||||
const URL = "https://chain.so/api/v2/";
|
||||
|
||||
const fetch_api = btcOperator.fetch = function (api) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.debug(URL + api);
|
||||
fetch(URL + api).then(response => {
|
||||
response.json()
|
||||
.then(result => result.status === "success" ? resolve(result) : reject(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
};
|
||||
|
||||
const SATOSHI_IN_BTC = 1e8;
|
||||
|
||||
function get_fee_rate() {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch('https://api.blockchain.info/mempool/fees').then(response => {
|
||||
if (response.ok)
|
||||
response.json()
|
||||
.then(result => resolve(parseFloat((result.regular / SATOSHI_IN_BTC).toFixed(8))))
|
||||
.catch(error => reject(error));
|
||||
else
|
||||
reject(response);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
const broadcastTx = btcOperator.broadcastTx = rawTxHex => new Promise((resolve, reject) => {
|
||||
let url = 'https://coinb.in/api/?uid=1&key=12345678901234567890123456789012&setmodule=bitcoin&request=sendrawtransaction';
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: "rawtx=" + rawTxHex
|
||||
}).then(response => {
|
||||
response.text().then(resultText => {
|
||||
let r = resultText.match(/<result>.*<\/result>/);
|
||||
if (!r)
|
||||
reject(resultText);
|
||||
else {
|
||||
r = r.pop().replace('<result>', '').replace('</result>', '');
|
||||
if (r == '1') {
|
||||
let txid = resultText.match(/<txid>.*<\/txid>/).pop().replace('<txid>', '').replace('</txid>', '');
|
||||
resolve(txid);
|
||||
} else if (r == '0') {
|
||||
let error = resultText.match(/<response>.*<\/response>/).pop().replace('<response>', '').replace('</response>', '');
|
||||
reject(decodeURIComponent(error.replace(/\+/g, " ")));
|
||||
} else reject(resultText);
|
||||
}
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
|
||||
Object.defineProperties(btcOperator, {
|
||||
newKeys: {
|
||||
get: () => {
|
||||
let r = coinjs.newKeys();
|
||||
r.segwitAddress = coinjs.segwitAddress(r.pubkey).address;
|
||||
r.bech32Address = coinjs.bech32Address(r.pubkey).address;
|
||||
return r;
|
||||
}
|
||||
},
|
||||
pubkey: {
|
||||
value: key => key.length >= 66 ? key : (key.length == 64 ? coinjs.newPubkey(key) : coinjs.wif2pubkey(key).pubkey)
|
||||
},
|
||||
address: {
|
||||
value: (key, prefix = undefined) => coinjs.pubkey2address(btcOperator.pubkey(key), prefix)
|
||||
},
|
||||
segwitAddress: {
|
||||
value: key => coinjs.segwitAddress(btcOperator.pubkey(key)).address
|
||||
},
|
||||
bech32Address: {
|
||||
value: key => coinjs.bech32Address(btcOperator.pubkey(key)).address
|
||||
}
|
||||
});
|
||||
|
||||
coinjs.compressed = true;
|
||||
|
||||
const verifyKey = btcOperator.verifyKey = function (addr, key) {
|
||||
if (!addr || !key)
|
||||
return undefined;
|
||||
switch (coinjs.addressDecode(addr).type) {
|
||||
case "standard":
|
||||
return btcOperator.address(key) === addr;
|
||||
case "multisig":
|
||||
return btcOperator.segwitAddress(key) === addr;
|
||||
case "bech32":
|
||||
return btcOperator.bech32Address(key) === addr;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const validateAddress = btcOperator.validateAddress = function (addr) {
|
||||
if (!addr)
|
||||
return undefined;
|
||||
let type = coinjs.addressDecode(addr).type;
|
||||
if (["standard", "multisig", "bech32"].includes(type))
|
||||
return type;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
btcOperator.multiSigAddress = function (pubKeys, minRequired) {
|
||||
if (!Array.isArray(pubKeys))
|
||||
throw "pubKeys must be an array of public keys";
|
||||
else if (pubKeys.length < minRequired)
|
||||
throw "minimum required should be less than the number of pubKeys";
|
||||
return coinjs.pubkeys2MultisigAddress(pubKeys, minRequired);
|
||||
}
|
||||
|
||||
//convert from one blockchain to another blockchain (target version)
|
||||
btcOperator.convert = {};
|
||||
|
||||
btcOperator.convert.wif = function (source_wif, target_version = coinjs.priv) {
|
||||
let keyHex = decodeLegacy(source_wif).hex;
|
||||
if (!keyHex || keyHex.length < 66 || !/01$/.test(keyHex))
|
||||
return null;
|
||||
else
|
||||
return encodeLegacy(keyHex, target_version);
|
||||
}
|
||||
|
||||
btcOperator.convert.legacy2legacy = function (source_addr, target_version = coinjs.pub) {
|
||||
let rawHex = decodeLegacy(source_addr).hex;
|
||||
if (!rawHex)
|
||||
return null;
|
||||
else
|
||||
return encodeLegacy(rawHex, target_version);
|
||||
}
|
||||
|
||||
btcOperator.convert.legacy2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) {
|
||||
let rawHex = decodeLegacy(source_addr).hex;
|
||||
if (!rawHex)
|
||||
return null;
|
||||
else
|
||||
return encodeBech32(rawHex, target_version, target_hrp);
|
||||
}
|
||||
|
||||
btcOperator.convert.bech2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) {
|
||||
let rawHex = decodeBech32(source_addr).hex;
|
||||
if (!rawHex)
|
||||
return null;
|
||||
else
|
||||
return encodeBech32(rawHex, target_version, target_hrp);
|
||||
}
|
||||
|
||||
btcOperator.convert.bech2legacy = function (source_addr, target_version = coinjs.pub) {
|
||||
let rawHex = decodeBech32(source_addr).hex;
|
||||
if (!rawHex)
|
||||
return null;
|
||||
else
|
||||
return encodeLegacy(rawHex, target_version);
|
||||
}
|
||||
|
||||
function decodeLegacy(source) {
|
||||
var decode = coinjs.base58decode(source);
|
||||
var raw = decode.slice(0, decode.length - 4),
|
||||
checksum = decode.slice(decode.length - 4);
|
||||
var hash = Crypto.SHA256(Crypto.SHA256(raw, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
if (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3])
|
||||
return null;
|
||||
let version = raw.shift();
|
||||
return {
|
||||
version: version,
|
||||
hex: Crypto.util.bytesToHex(raw)
|
||||
}
|
||||
}
|
||||
|
||||
function encodeLegacy(hex, version) {
|
||||
var bytes = Crypto.util.hexToBytes(hex);
|
||||
bytes.unshift(version);
|
||||
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
var checksum = hash.slice(0, 4);
|
||||
return coinjs.base58encode(bytes.concat(checksum));
|
||||
}
|
||||
|
||||
function decodeBech32(source) {
|
||||
let decode = coinjs.bech32_decode(source);
|
||||
if (!decode)
|
||||
return null;
|
||||
var raw = decode.data;
|
||||
let version = raw.shift();
|
||||
raw = coinjs.bech32_convert(raw, 5, 8, false);
|
||||
return {
|
||||
hrp: decode.hrp,
|
||||
version: version,
|
||||
hex: Crypto.util.bytesToHex(raw)
|
||||
}
|
||||
}
|
||||
|
||||
function encodeBech32(hex, version, hrp) {
|
||||
var bytes = Crypto.util.hexToBytes(hex);
|
||||
bytes = coinjs.bech32_convert(bytes, 8, 5, true);
|
||||
bytes.unshift(version)
|
||||
return coinjs.bech32_encode(hrp, bytes);
|
||||
}
|
||||
|
||||
//BTC blockchain APIs
|
||||
|
||||
btcOperator.getBalance = addr => new Promise((resolve, reject) => {
|
||||
fetch_api(`get_address_balance/BTC/${addr}`)
|
||||
.then(result => resolve(parseFloat(result.data.confirmed_balance)))
|
||||
.catch(error => reject(error))
|
||||
});
|
||||
|
||||
const BASE_TX_SIZE = 12,
|
||||
BASE_INPUT_SIZE = 41,
|
||||
LEGACY_INPUT_SIZE = 107,
|
||||
BECH32_INPUT_SIZE = 27,
|
||||
SEGWIT_INPUT_SIZE = 59,
|
||||
MULTISIG_INPUT_SIZE_ES = 351,
|
||||
BASE_OUTPUT_SIZE = 9,
|
||||
LEGACY_OUTPUT_SIZE = 25,
|
||||
BECH32_OUTPUT_SIZE = 23,
|
||||
SEGWIT_OUTPUT_SIZE = 23;
|
||||
|
||||
function _redeemScript(addr, key) {
|
||||
let decode = coinjs.addressDecode(addr);
|
||||
switch (decode.type) {
|
||||
case "standard":
|
||||
return false;
|
||||
case "multisig":
|
||||
return key ? coinjs.segwitAddress(btcOperator.pubkey(key)).redeemscript : null;
|
||||
case "bech32":
|
||||
return decode.redeemscript;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function _sizePerInput(addr, rs) {
|
||||
switch (coinjs.addressDecode(addr).type) {
|
||||
case "standard":
|
||||
return BASE_INPUT_SIZE + LEGACY_INPUT_SIZE;
|
||||
case "bech32":
|
||||
return BASE_INPUT_SIZE + BECH32_INPUT_SIZE;
|
||||
case "multisig":
|
||||
switch (coinjs.script().decodeRedeemScript(rs).type) {
|
||||
case "segwit__":
|
||||
return BASE_INPUT_SIZE + SEGWIT_INPUT_SIZE;
|
||||
case "multisig__":
|
||||
return BASE_INPUT_SIZE + MULTISIG_INPUT_SIZE_ES;
|
||||
default:
|
||||
return null;
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function _sizePerOutput(addr) {
|
||||
switch (coinjs.addressDecode(addr).type) {
|
||||
case "standard":
|
||||
return BASE_OUTPUT_SIZE + LEGACY_OUTPUT_SIZE;
|
||||
case "bech32":
|
||||
return BASE_OUTPUT_SIZE + BECH32_OUTPUT_SIZE;
|
||||
case "multisig":
|
||||
return BASE_OUTPUT_SIZE + SEGWIT_OUTPUT_SIZE;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function validateTxParameters(parameters) {
|
||||
let invalids = [];
|
||||
//sender-ids
|
||||
if (parameters.senders) {
|
||||
if (!Array.isArray(parameters.senders))
|
||||
parameters.senders = [parameters.senders];
|
||||
parameters.senders.forEach(id => !validateAddress(id) ? invalids.push(id) : null);
|
||||
if (invalids.length)
|
||||
throw "Invalid senders:" + invalids;
|
||||
}
|
||||
if (parameters.privkeys) {
|
||||
if (!Array.isArray(parameters.privkeys))
|
||||
parameters.privkeys = [parameters.privkeys];
|
||||
if (parameters.senders.length != parameters.privkeys.length)
|
||||
throw "Array length for senders and privkeys should be equal";
|
||||
parameters.senders.forEach((id, i) => {
|
||||
let key = parameters.privkeys[i];
|
||||
if (!verifyKey(id, key)) //verify private-key
|
||||
invalids.push(id);
|
||||
if (key.length === 64) //convert Hex to WIF if needed
|
||||
parameters.privkeys[i] = coinjs.privkey2wif(key);
|
||||
});
|
||||
if (invalids.length)
|
||||
throw "Invalid keys:" + invalids;
|
||||
}
|
||||
//receiver-ids (and change-id)
|
||||
if (!Array.isArray(parameters.receivers))
|
||||
parameters.receivers = [parameters.receivers];
|
||||
parameters.receivers.forEach(id => !validateAddress(id) ? invalids.push(id) : null);
|
||||
if (invalids.length)
|
||||
throw "Invalid receivers:" + invalids;
|
||||
if (parameters.change_address && !validateAddress(parameters.change_address))
|
||||
throw "Invalid change_address:" + parameters.change_address;
|
||||
//fee and amounts
|
||||
if ((typeof parameters.fee !== "number" || parameters.fee <= 0) && parameters.fee !== null) //fee = null (auto calc)
|
||||
throw "Invalid fee:" + parameters.fee;
|
||||
if (!Array.isArray(parameters.amounts))
|
||||
parameters.amounts = [parameters.amounts];
|
||||
if (parameters.receivers.length != parameters.amounts.length)
|
||||
throw "Array length for receivers and amounts should be equal";
|
||||
parameters.amounts.forEach(a => typeof a !== "number" || a <= 0 ? invalids.push(a) : null);
|
||||
if (invalids.length)
|
||||
throw "Invalid amounts:" + invalids;
|
||||
//return
|
||||
return parameters;
|
||||
}
|
||||
|
||||
function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_address, fee_from_receiver) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let total_amount = parseFloat(amounts.reduce((t, a) => t + a, 0).toFixed(8));
|
||||
const tx = coinjs.transaction();
|
||||
let output_size = addOutputs(tx, receivers, amounts, change_address);
|
||||
addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver).then(result => {
|
||||
if (result.change_amount > 0) //add change amount if any
|
||||
tx.outs[tx.outs.length - 1].value = parseInt(result.change_amount * SATOSHI_IN_BTC); //values are in satoshi
|
||||
if (fee_from_receiver) { //deduce fee from receivers if fee_from_receiver
|
||||
let fee_remaining = parseInt(result.fee * SATOSHI_IN_BTC);
|
||||
for (let i = 0; i < tx.outs.length - 1 && fee_remaining > 0; i++) {
|
||||
if (fee_remaining < tx.outs[i].value) {
|
||||
tx.outs[i].value -= fee_remaining;
|
||||
fee_remaining = 0;
|
||||
} else {
|
||||
fee_remaining -= tx.outs[i].value;
|
||||
tx.outs[i].value = 0;
|
||||
}
|
||||
}
|
||||
if (fee_remaining > 0)
|
||||
return reject("Send amount is less than fee");
|
||||
|
||||
}
|
||||
tx.outs = tx.outs.filter(o => o.value != 0); //remove all output with value 0
|
||||
result.output_size = output_size;
|
||||
result.output_amount = total_amount - (fee_from_receiver ? result.fee : 0);
|
||||
result.total_size = BASE_TX_SIZE + output_size + result.input_size;
|
||||
result.transaction = tx;
|
||||
resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (fee !== null) {
|
||||
addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false).then(result => {
|
||||
result.fee = fee;
|
||||
resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
} else {
|
||||
get_fee_rate().then(fee_rate => {
|
||||
let net_fee = BASE_TX_SIZE * fee_rate;
|
||||
net_fee += (output_size * fee_rate);
|
||||
(fee_from_receiver ?
|
||||
addUTXOs(tx, senders, redeemScripts, total_amount, false) :
|
||||
addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate)
|
||||
).then(result => {
|
||||
result.fee = parseFloat((net_fee + (result.input_size * fee_rate)).toFixed(8));
|
||||
result.fee_rate = fee_rate;
|
||||
resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
required_amount = parseFloat(required_amount.toFixed(8));
|
||||
if (typeof rec_args.n === "undefined") {
|
||||
rec_args.n = 0;
|
||||
rec_args.input_size = 0;
|
||||
rec_args.input_amount = 0;
|
||||
}
|
||||
if (required_amount <= 0)
|
||||
return resolve({
|
||||
input_size: rec_args.input_size,
|
||||
input_amount: rec_args.input_amount,
|
||||
change_amount: required_amount * -1 //required_amount will be -ve of change_amount
|
||||
});
|
||||
else if (rec_args.n >= senders.length)
|
||||
return reject("Insufficient Balance");
|
||||
let addr = senders[rec_args.n],
|
||||
rs = redeemScripts[rec_args.n];
|
||||
let size_per_input = _sizePerInput(addr, rs);
|
||||
fetch_api(`get_tx_unspent/BTC/${addr}`).then(result => {
|
||||
let utxos = result.data.txs;
|
||||
console.debug("add-utxo", addr, rs, required_amount, utxos);
|
||||
for (let i = 0; i < utxos.length && required_amount > 0; i++) {
|
||||
if (!utxos[i].confirmations) //ignore unconfirmed utxo
|
||||
continue;
|
||||
var script;
|
||||
if (!rs || !rs.length) //legacy script
|
||||
script = utxos[i].script_hex;
|
||||
else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi))) {
|
||||
//redeemScript for segwit/bech32
|
||||
let s = coinjs.script();
|
||||
s.writeBytes(Crypto.util.hexToBytes(rs));
|
||||
s.writeOp(0);
|
||||
s.writeBytes(coinjs.numToBytes((utxos[i].value * SATOSHI_IN_BTC).toFixed(0), 8));
|
||||
script = Crypto.util.bytesToHex(s.buffer);
|
||||
} else //redeemScript for multisig
|
||||
script = rs;
|
||||
tx.addinput(utxos[i].txid, utxos[i].output_no, script, 0xfffffffd /*sequence*/); //0xfffffffd for Replace-by-fee
|
||||
//update track values
|
||||
rec_args.input_size += size_per_input;
|
||||
rec_args.input_amount += parseFloat(utxos[i].value);
|
||||
required_amount -= parseFloat(utxos[i].value);
|
||||
if (fee_rate) //automatic fee calculation (dynamic)
|
||||
required_amount += size_per_input * fee_rate;
|
||||
}
|
||||
rec_args.n += 1;
|
||||
addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function addOutputs(tx, receivers, amounts, change_address) {
|
||||
let size = 0;
|
||||
for (let i in receivers) {
|
||||
tx.addoutput(receivers[i], amounts[i]);
|
||||
size += _sizePerOutput(receivers[i]);
|
||||
}
|
||||
tx.addoutput(change_address, 0);
|
||||
size += _sizePerOutput(change_address);
|
||||
return size;
|
||||
}
|
||||
|
||||
/*
|
||||
function autoFeeCalc(tx) {
|
||||
return new Promise((resolve, reject) => {
|
||||
get_fee_rate().then(fee_rate => {
|
||||
let tx_size = tx.size();
|
||||
for (var i = 0; i < this.ins.length; i++)
|
||||
switch (tx.extractScriptKey(i).type) {
|
||||
case 'scriptpubkey':
|
||||
tx_size += SIGN_SIZE;
|
||||
break;
|
||||
case 'segwit':
|
||||
case 'multisig':
|
||||
tx_size += SIGN_SIZE * 0.25;
|
||||
break;
|
||||
default:
|
||||
console.warn('Unknown script-type');
|
||||
tx_size += SIGN_SIZE;
|
||||
}
|
||||
resolve(tx_size * fee_rate);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function editFee(tx, current_fee, target_fee, index = -1) {
|
||||
//values are in satoshi
|
||||
index = parseInt(index >= 0 ? index : tx.outs.length - index);
|
||||
if (index < 0 || index >= tx.outs.length)
|
||||
throw "Invalid index";
|
||||
let edit_value = parseInt(current_fee - target_fee), //rip of any decimal places
|
||||
current_value = tx.outs[index].value; //could be BigInterger
|
||||
if (edit_value < 0 && edit_value > current_value)
|
||||
throw "Insufficient value at vout";
|
||||
tx.outs[index].value = current_value instanceof BigInteger ?
|
||||
current_value.add(new BigInteger('' + edit_value)) : parseInt(current_value + edit_value);
|
||||
}
|
||||
*/
|
||||
|
||||
btcOperator.sendTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
createSignedTx(senders, privkeys, receivers, amounts, fee, options).then(result => {
|
||||
debugger;
|
||||
broadcastTx(result.transaction.serialize())
|
||||
.then(txid => resolve(txid))
|
||||
.catch(error => reject(error));
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
const createSignedTx = btcOperator.createSignedTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
({
|
||||
senders,
|
||||
privkeys,
|
||||
receivers,
|
||||
amounts
|
||||
} = validateTxParameters({
|
||||
senders,
|
||||
privkeys,
|
||||
receivers,
|
||||
amounts,
|
||||
fee,
|
||||
change_address: options.change_address
|
||||
}));
|
||||
} catch (e) {
|
||||
return reject(e)
|
||||
}
|
||||
let redeemScripts = [],
|
||||
wif_keys = [];
|
||||
for (let i in senders) {
|
||||
let rs = _redeemScript(senders[i], privkeys[i]); //get redeem-script (segwit/bech32)
|
||||
redeemScripts.push(rs);
|
||||
rs === false ? wif_keys.unshift(privkeys[i]) : wif_keys.push(privkeys[i]); //sorting private-keys (wif)
|
||||
}
|
||||
if (redeemScripts.includes(null)) //TODO: segwit
|
||||
return reject("Unable to get redeem-script");
|
||||
//create transaction
|
||||
createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => {
|
||||
let tx = result.transaction;
|
||||
console.debug("Unsigned:", tx.serialize());
|
||||
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());
|
||||
resolve(result);
|
||||
}).catch(error => reject(error));
|
||||
})
|
||||
}
|
||||
|
||||
btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
({
|
||||
senders,
|
||||
receivers,
|
||||
amounts
|
||||
} = validateTxParameters({
|
||||
senders,
|
||||
receivers,
|
||||
amounts,
|
||||
fee,
|
||||
change_address: options.change_address
|
||||
}));
|
||||
} catch (e) {
|
||||
return reject(e)
|
||||
}
|
||||
let redeemScripts = senders.map(id => _redeemScript(id));
|
||||
if (redeemScripts.includes(null)) //TODO: segwit
|
||||
return reject("Unable to get redeem-script");
|
||||
//create transaction
|
||||
createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => {
|
||||
result.tx_hex = result.transaction.serialize();
|
||||
delete result.transaction;
|
||||
resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
btcOperator.createMultiSigTx = function (sender, redeemScript, receivers, amounts, fee = null, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
//validate tx parameters
|
||||
if (validateAddress(sender) !== "multisig")
|
||||
return reject("Invalid sender (multisig):" + sender);
|
||||
else {
|
||||
let script = coinjs.script();
|
||||
let decode = script.decodeRedeemScript(redeemScript);
|
||||
if (!decode || decode.address !== sender)
|
||||
return reject("Invalid redeem-script");
|
||||
}
|
||||
try {
|
||||
({
|
||||
receivers,
|
||||
amounts
|
||||
} = validateTxParameters({
|
||||
receivers,
|
||||
amounts,
|
||||
fee,
|
||||
change_address: options.change_address
|
||||
}));
|
||||
} catch (e) {
|
||||
return reject(e)
|
||||
}
|
||||
//create transaction
|
||||
createTransaction([sender], [redeemScript], receivers, amounts, fee, options.change_address || sender, options.fee_from_receiver).then(result => {
|
||||
result.tx_hex = result.transaction.serialize();
|
||||
delete result.transaction;
|
||||
resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function deserializeTx(tx) {
|
||||
if (typeof tx === 'string' || Array.isArray(tx)) {
|
||||
try {
|
||||
tx = coinjs.transaction().deserialize(tx);
|
||||
} catch {
|
||||
throw "Invalid transaction hex";
|
||||
}
|
||||
} else if (typeof tx !== 'object' || typeof tx.sign !== 'function')
|
||||
throw "Invalid transaction object";
|
||||
return tx;
|
||||
}
|
||||
|
||||
btcOperator.signTx = function (tx, privkeys, sighashtype = 1) {
|
||||
tx = deserializeTx(tx);
|
||||
if (!Array.isArray(privkeys))
|
||||
privkeys = [privkeys];
|
||||
for (let i in privkeys)
|
||||
if (privkeys[i].length === 64)
|
||||
privkeys[i] = coinjs.privkey2wif(privkeys[i]);
|
||||
new Set(privkeys).forEach(key => tx.sign(key, sighashtype)); //Sign the tx using private key WIF
|
||||
return tx.serialize();
|
||||
}
|
||||
|
||||
const checkSigned = btcOperator.checkSigned = function (tx, bool = true) {
|
||||
tx = deserializeTx(tx);
|
||||
let n = [];
|
||||
for (let i in tx.ins) {
|
||||
var s = tx.extractScriptKey(i);
|
||||
if (s['type'] !== 'multisig')
|
||||
n.push(s.signed == 'true' || (tx.witness[i] && tx.witness[i].length == 2))
|
||||
else {
|
||||
var rs = coinjs.script().decodeRedeemScript(s.script);
|
||||
let x = {
|
||||
s: s['signatures'],
|
||||
r: rs['signaturesRequired'],
|
||||
t: rs['pubkeys'].length
|
||||
};
|
||||
if (x.r > x.t)
|
||||
throw "signaturesRequired is more than publicKeys";
|
||||
else if (x.s < x.r)
|
||||
n.push(x);
|
||||
else
|
||||
n.push(true);
|
||||
}
|
||||
}
|
||||
return bool ? !(n.filter(x => x !== true).length) : n;
|
||||
}
|
||||
|
||||
btcOperator.checkIfSameTx = function (tx1, tx2) {
|
||||
tx1 = deserializeTx(tx1);
|
||||
tx2 = deserializeTx(tx2);
|
||||
if (tx1.ins.length !== tx2.ins.length || tx1.outs.length !== tx2.outs.length)
|
||||
return false;
|
||||
for (let i = 0; i < tx1.ins.length; i++)
|
||||
if (tx1.ins[i].outpoint.hash !== tx2.ins[i].outpoint.hash || tx1.ins[i].outpoint.index !== tx2.ins[i].outpoint.index)
|
||||
return false;
|
||||
for (let i = 0; i < tx2.ins.length; i++)
|
||||
if (tx1.outs[i].value !== tx2.outs[i].value || Crypto.util.bytesToHex(tx1.outs[i].script.buffer) !== Crypto.util.bytesToHex(tx2.outs[i].script.buffer))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const getTxOutput = (txid, i) => new Promise((resolve, reject) => {
|
||||
fetch_api(`get_tx_outputs/BTC/${txid}/${i}`)
|
||||
.then(result => resolve(result.data.outputs))
|
||||
.catch(error => reject(error))
|
||||
});
|
||||
|
||||
btcOperator.parseTransaction = function (tx) {
|
||||
return new Promise((resolve, reject) => {
|
||||
tx = deserializeTx(tx);
|
||||
let result = {};
|
||||
let promises = [];
|
||||
//Parse Inputs
|
||||
for (let i = 0; i < tx.ins.length; i++)
|
||||
promises.push(getTxOutput(tx.ins[i].outpoint.hash, tx.ins[i].outpoint.index));
|
||||
Promise.all(promises).then(inputs => {
|
||||
result.inputs = inputs.map(inp => Object({
|
||||
address: inp.address,
|
||||
value: parseFloat(inp.value)
|
||||
}));
|
||||
let signed = checkSigned(tx, false);
|
||||
result.inputs.forEach((inp, i) => inp.signed = signed[i]);
|
||||
//Parse Outputs
|
||||
result.outputs = tx.outs.map(out => {
|
||||
var address;
|
||||
switch (out.script.chunks[0]) {
|
||||
case 0: //bech32
|
||||
address = encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.bech32.version, coinjs.bech32.hrp);
|
||||
break;
|
||||
case 169: //multisig, segwit
|
||||
address = encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.multisig);
|
||||
break;
|
||||
case 118: //legacy
|
||||
address = encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[2]), coinjs.pub);
|
||||
}
|
||||
return {
|
||||
address,
|
||||
value: parseFloat(out.value / SATOSHI_IN_BTC)
|
||||
}
|
||||
});
|
||||
//Parse Totals
|
||||
result.total_input = parseFloat(result.inputs.reduce((a, inp) => a += inp.value, 0).toFixed(8));
|
||||
result.total_output = parseFloat(result.outputs.reduce((a, out) => a += out.value, 0).toFixed(8));
|
||||
result.fee = parseFloat((result.total_input - result.total_output).toFixed(8));
|
||||
resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
btcOperator.transactionID = function (tx) {
|
||||
tx = deserializeTx(tx);
|
||||
let clone = coinjs.clone(tx);
|
||||
clone.witness = null;
|
||||
let raw_bytes = Crypto.util.hexToBytes(clone.serialize());
|
||||
let txid = Crypto.SHA256(Crypto.SHA256(raw_bytes, { asBytes: true }), { asBytes: true }).reverse();
|
||||
return Crypto.util.bytesToHex(txid);
|
||||
}
|
||||
|
||||
btcOperator.getTx = txid => new Promise((resolve, reject) => {
|
||||
fetch_api(`get_tx/BTC/${txid}`)
|
||||
.then(result => resolve(result.data))
|
||||
.catch(error => reject(error))
|
||||
});
|
||||
|
||||
btcOperator.getAddressData = addr => new Promise((resolve, reject) => {
|
||||
fetch_api(`address/BTC/${addr}`)
|
||||
.then(result => resolve(result.data))
|
||||
.catch(error => reject(error))
|
||||
});
|
||||
|
||||
btcOperator.getBlock = block => new Promise((resolve, reject) => {
|
||||
fetch_api(`get_block/BTC/${block}`)
|
||||
.then(result => resolve(result.data))
|
||||
.catch(error => reject(error))
|
||||
});
|
||||
|
||||
})('object' === typeof module ? module.exports : window.btcOperator = {});
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,339 +1,442 @@
|
||||
'use strict';
|
||||
(function (EXPORTS) { //floCrypto v2.3.3e
|
||||
/* FLO Crypto Operators */
|
||||
'use strict';
|
||||
const floCrypto = EXPORTS;
|
||||
|
||||
(function(GLOBAL) {
|
||||
const floCrypto = GLOBAL.floCrypto = {
|
||||
const p = BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16);
|
||||
const ecparams = EllipticCurve.getSECCurveByName("secp256k1");
|
||||
const ascii_alternatives = `‘ '\n’ '\n“ "\n” "\n– --\n— ---\n≥ >=\n≤ <=\n≠ !=\n× *\n÷ /\n← <-\n→ ->\n↔ <->\n⇒ =>\n⇐ <=\n⇔ <=>`;
|
||||
const exponent1 = () => p.add(BigInteger.ONE).divide(BigInteger("4"));
|
||||
coinjs.compressed = true; //defaulting coinjs compressed to true;
|
||||
|
||||
util: {
|
||||
p: BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16),
|
||||
function calculateY(x) {
|
||||
let exp = exponent1();
|
||||
// x is x value of public key in BigInteger format without 02 or 03 or 04 prefix
|
||||
return x.modPow(BigInteger("3"), p).add(BigInteger("7")).mod(p).modPow(exp, p)
|
||||
}
|
||||
|
||||
ecparams: EllipticCurve.getSECCurveByName("secp256k1"),
|
||||
function getUncompressedPublicKey(compressedPublicKey) {
|
||||
// Fetch x from compressedPublicKey
|
||||
let pubKeyBytes = Crypto.util.hexToBytes(compressedPublicKey);
|
||||
const prefix = pubKeyBytes.shift() // remove prefix
|
||||
let prefix_modulus = prefix % 2;
|
||||
pubKeyBytes.unshift(0) // add prefix 0
|
||||
let x = new BigInteger(pubKeyBytes)
|
||||
let xDecimalValue = x.toString()
|
||||
// Fetch y
|
||||
let y = calculateY(x);
|
||||
let yDecimalValue = y.toString();
|
||||
// verify y value
|
||||
let resultBigInt = y.mod(BigInteger("2"));
|
||||
let check = resultBigInt.toString() % 2;
|
||||
if (prefix_modulus !== check)
|
||||
yDecimalValue = y.negate().mod(p).toString();
|
||||
return {
|
||||
x: xDecimalValue,
|
||||
y: yDecimalValue
|
||||
};
|
||||
}
|
||||
|
||||
asciiAlternatives: `‘ '\n’ '\n“ "\n” "\n– --\n— ---\n≥ >=\n≤ <=\n≠ !=\n× *\n÷ /\n← <-\n→ ->\n↔ <->\n⇒ =>\n⇐ <=\n⇔ <=>`,
|
||||
|
||||
exponent1: function() {
|
||||
return this.p.add(BigInteger.ONE).divide(BigInteger("4"))
|
||||
},
|
||||
|
||||
calculateY: function(x) {
|
||||
let p = this.p;
|
||||
let exp = this.exponent1();
|
||||
// x is x value of public key in BigInteger format without 02 or 03 or 04 prefix
|
||||
return x.modPow(BigInteger("3"), p).add(BigInteger("7")).mod(p).modPow(exp, p)
|
||||
},
|
||||
getUncompressedPublicKey: function(compressedPublicKey) {
|
||||
const p = this.p;
|
||||
// Fetch x from compressedPublicKey
|
||||
let pubKeyBytes = Crypto.util.hexToBytes(compressedPublicKey);
|
||||
const prefix = pubKeyBytes.shift() // remove prefix
|
||||
let prefix_modulus = prefix % 2;
|
||||
pubKeyBytes.unshift(0) // add prefix 0
|
||||
let x = new BigInteger(pubKeyBytes)
|
||||
let xDecimalValue = x.toString()
|
||||
// Fetch y
|
||||
let y = this.calculateY(x);
|
||||
let yDecimalValue = y.toString();
|
||||
// verify y value
|
||||
let resultBigInt = y.mod(BigInteger("2"));
|
||||
let check = resultBigInt.toString() % 2;
|
||||
if (prefix_modulus !== check)
|
||||
yDecimalValue = y.negate().mod(p).toString();
|
||||
return {
|
||||
x: xDecimalValue,
|
||||
y: yDecimalValue
|
||||
};
|
||||
},
|
||||
|
||||
getSenderPublicKeyString: function() {
|
||||
let privateKey = ellipticCurveEncryption.senderRandom();
|
||||
var senderPublicKeyString = ellipticCurveEncryption.senderPublicString(privateKey);
|
||||
return {
|
||||
privateKey: privateKey,
|
||||
senderPublicKeyString: senderPublicKeyString
|
||||
}
|
||||
},
|
||||
|
||||
deriveSharedKeySender: function(receiverCompressedPublicKey, senderPrivateKey) {
|
||||
let receiverPublicKeyString = this.getUncompressedPublicKey(receiverCompressedPublicKey);
|
||||
var senderDerivedKey = ellipticCurveEncryption.senderSharedKeyDerivation(
|
||||
receiverPublicKeyString.x, receiverPublicKeyString.y, senderPrivateKey);
|
||||
return senderDerivedKey;
|
||||
},
|
||||
|
||||
deriveReceiverSharedKey: function(senderPublicKeyString, receiverPrivateKey) {
|
||||
return ellipticCurveEncryption.receiverSharedKeyDerivation(
|
||||
senderPublicKeyString.XValuePublicString, senderPublicKeyString.YValuePublicString, receiverPrivateKey);
|
||||
},
|
||||
|
||||
getReceiverPublicKeyString: function(privateKey) {
|
||||
return ellipticCurveEncryption.receiverPublicString(privateKey);
|
||||
},
|
||||
|
||||
deriveSharedKeyReceiver: function(senderPublicKeyString, receiverPrivateKey) {
|
||||
return ellipticCurveEncryption.receiverSharedKeyDerivation(
|
||||
senderPublicKeyString.XValuePublicString, senderPublicKeyString.YValuePublicString, receiverPrivateKey);
|
||||
},
|
||||
|
||||
wifToDecimal: function(pk_wif, isPubKeyCompressed = false) {
|
||||
let pk = Bitcoin.Base58.decode(pk_wif)
|
||||
pk.shift()
|
||||
pk.splice(-4, 4)
|
||||
//If the private key corresponded to a compressed public key, also drop the last byte (it should be 0x01).
|
||||
if (isPubKeyCompressed == true) pk.pop()
|
||||
pk.unshift(0)
|
||||
let privateKeyDecimal = BigInteger(pk).toString()
|
||||
let privateKeyHex = Crypto.util.bytesToHex(pk)
|
||||
return {
|
||||
privateKeyDecimal: privateKeyDecimal,
|
||||
privateKeyHex: privateKeyHex
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
//generate a random Interger within range
|
||||
randInt: function(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
},
|
||||
|
||||
//generate a random String within length (options : alphaNumeric chars only)
|
||||
randString: function(length, alphaNumeric = true) {
|
||||
var result = '';
|
||||
if (alphaNumeric)
|
||||
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
else
|
||||
var characters =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():';
|
||||
for (var i = 0; i < length; i++)
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
return result;
|
||||
},
|
||||
|
||||
//Encrypt Data using public-key
|
||||
encryptData: function(data, receiverCompressedPublicKey) {
|
||||
var senderECKeyData = this.util.getSenderPublicKeyString();
|
||||
var senderDerivedKey = this.util.deriveSharedKeySender(receiverCompressedPublicKey, senderECKeyData
|
||||
.privateKey);
|
||||
let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue;
|
||||
let secret = Crypto.AES.encrypt(data, senderKey);
|
||||
return {
|
||||
secret: secret,
|
||||
senderPublicKeyString: senderECKeyData.senderPublicKeyString
|
||||
};
|
||||
},
|
||||
|
||||
//Decrypt Data using private-key
|
||||
decryptData: function(data, myPrivateKey) {
|
||||
var receiverECKeyData = {};
|
||||
if (typeof myPrivateKey !== "string") throw new Error("No private key found.");
|
||||
|
||||
let privateKey = this.util.wifToDecimal(myPrivateKey, true);
|
||||
if (typeof privateKey.privateKeyDecimal !== "string") throw new Error(
|
||||
"Failed to detremine your private key.");
|
||||
receiverECKeyData.privateKey = privateKey.privateKeyDecimal;
|
||||
|
||||
var receiverDerivedKey = this.util.deriveReceiverSharedKey(data.senderPublicKeyString,
|
||||
receiverECKeyData
|
||||
.privateKey);
|
||||
|
||||
let receiverKey = receiverDerivedKey.XValue + receiverDerivedKey.YValue;
|
||||
let decryptMsg = Crypto.AES.decrypt(data.secret, receiverKey);
|
||||
return decryptMsg;
|
||||
},
|
||||
|
||||
//Sign data using private-key
|
||||
signData: function(data, privateKeyHex) {
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
key.setCompressed(true);
|
||||
|
||||
var privateKeyArr = key.getBitcoinPrivateKeyByteArray();
|
||||
var privateKey = BigInteger.fromByteArrayUnsigned(privateKeyArr);
|
||||
var messageHash = Crypto.SHA256(data);
|
||||
|
||||
var messageHashBigInteger = new BigInteger(messageHash);
|
||||
var messageSign = Bitcoin.ECDSA.sign(messageHashBigInteger, key.priv);
|
||||
|
||||
var sighex = Crypto.util.bytesToHex(messageSign);
|
||||
return sighex;
|
||||
},
|
||||
|
||||
//Verify signatue of the data using public-key
|
||||
verifySign: function(data, signatureHex, publicKeyHex) {
|
||||
var msgHash = Crypto.SHA256(data);
|
||||
var messageHashBigInteger = new BigInteger(msgHash);
|
||||
|
||||
var sigBytes = Crypto.util.hexToBytes(signatureHex);
|
||||
var signature = Bitcoin.ECDSA.parseSig(sigBytes);
|
||||
|
||||
var publicKeyPoint = this.util.ecparams.getCurve().decodePointHex(publicKeyHex);
|
||||
|
||||
var verify = Bitcoin.ECDSA.verifyRaw(messageHashBigInteger, signature.r, signature.s,
|
||||
publicKeyPoint);
|
||||
return verify;
|
||||
},
|
||||
|
||||
//Generates a new flo ID and returns private-key, public-key and floID
|
||||
generateNewID: function() {
|
||||
try {
|
||||
var key = new Bitcoin.ECKey(false);
|
||||
key.setCompressed(true);
|
||||
return {
|
||||
floID: key.getBitcoinAddress(),
|
||||
pubKey: key.getPubKeyHex(),
|
||||
privKey: key.getBitcoinWalletImportFormat()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
|
||||
//Returns public-key from private-key
|
||||
getPubKeyHex: function(privateKeyHex) {
|
||||
if (!privateKeyHex)
|
||||
return null;
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return null;
|
||||
key.setCompressed(true);
|
||||
return key.getPubKeyHex();
|
||||
},
|
||||
|
||||
//Returns flo-ID from public-key or private-key
|
||||
getFloID: function(keyHex) {
|
||||
if (!keyHex)
|
||||
return null;
|
||||
try {
|
||||
var key = new Bitcoin.ECKey(keyHex);
|
||||
if (key.priv == null)
|
||||
key.setPub(keyHex);
|
||||
return key.getBitcoinAddress();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
//Verify the private-key for the given public-key or flo-ID
|
||||
verifyPrivKey: function(privateKeyHex, pubKey_floID, isfloID = true) {
|
||||
if (!privateKeyHex || !pubKey_floID)
|
||||
return false;
|
||||
try {
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return false;
|
||||
key.setCompressed(true);
|
||||
if (isfloID && pubKey_floID == key.getBitcoinAddress())
|
||||
return true;
|
||||
else if (!isfloID && pubKey_floID == key.getPubKeyHex())
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
|
||||
//Check if the given Address is valid or not
|
||||
validateAddr: function(inpAddr) {
|
||||
if (!inpAddr)
|
||||
return false;
|
||||
try {
|
||||
var addr = new Bitcoin.Address(inpAddr);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
//Split the str using shamir's Secret and Returns the shares
|
||||
createShamirsSecretShares: function(str, total_shares, threshold_limit) {
|
||||
try {
|
||||
if (str.length > 0) {
|
||||
var strHex = shamirSecretShare.str2hex(str);
|
||||
var shares = shamirSecretShare.share(strHex, total_shares, threshold_limit);
|
||||
return shares;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
//Verifies the shares and str
|
||||
verifyShamirsSecret: function(sharesArray, str) {
|
||||
return (str && this.retrieveShamirSecret(sharesArray) === str)
|
||||
},
|
||||
|
||||
//Returns the retrived secret by combining the shamirs shares
|
||||
retrieveShamirSecret: function(sharesArray) {
|
||||
try {
|
||||
if (sharesArray.length > 0) {
|
||||
var comb = shamirSecretShare.combine(sharesArray.slice(0, sharesArray.length));
|
||||
comb = shamirSecretShare.hex2str(comb);
|
||||
return comb;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
validateASCII: function(string, bool = true) {
|
||||
if (typeof string !== "string")
|
||||
return null;
|
||||
if (bool) {
|
||||
let x;
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
x = string.charCodeAt(i);
|
||||
if (x < 32 || x > 127)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
let x, invalids = {};
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
x = string.charCodeAt(i);
|
||||
if (x < 32 || x > 127)
|
||||
if (x in invalids)
|
||||
invalids[string[i]].push(i)
|
||||
else
|
||||
invalids[string[i]] = [i];
|
||||
}
|
||||
if (Object.keys(invalids).length)
|
||||
return invalids;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
convertToASCII: function(string, mode = 'soft-remove') {
|
||||
let chars = this.validateASCII(string, false);
|
||||
if (chars === true)
|
||||
return string;
|
||||
else if (chars === null)
|
||||
return null;
|
||||
let convertor, result = string,
|
||||
refAlt = {};
|
||||
this.util.asciiAlternatives.split('\n').forEach(a => refAlt[a[0]] = a.slice(2));
|
||||
mode = mode.toLowerCase();
|
||||
if (mode === "hard-unicode")
|
||||
convertor = (c) => `\\u${('000'+c.charCodeAt().toString(16)).slice(-4)}`;
|
||||
else if (mode === "soft-unicode")
|
||||
convertor = (c) => refAlt[c] || `\\u${('000'+c.charCodeAt().toString(16)).slice(-4)}`;
|
||||
else if (mode === "hard-remove")
|
||||
convertor = c => "";
|
||||
else if (mode === "soft-remove")
|
||||
convertor = c => refAlt[c] || "";
|
||||
else
|
||||
return null;
|
||||
for (let c in chars)
|
||||
result = result.replaceAll(c, convertor(c));
|
||||
return result;
|
||||
},
|
||||
|
||||
revertUnicode: function(string) {
|
||||
return string.replace(/\\u[\dA-F]{4}/gi,
|
||||
m => String.fromCharCode(parseInt(m.replace(/\\u/g, ''), 16)));
|
||||
function getSenderPublicKeyString() {
|
||||
let privateKey = ellipticCurveEncryption.senderRandom();
|
||||
var senderPublicKeyString = ellipticCurveEncryption.senderPublicString(privateKey);
|
||||
return {
|
||||
privateKey: privateKey,
|
||||
senderPublicKeyString: senderPublicKeyString
|
||||
}
|
||||
}
|
||||
|
||||
})(typeof global !== "undefined" ? global : window);
|
||||
function deriveSharedKeySender(receiverPublicKeyHex, senderPrivateKey) {
|
||||
let receiverPublicKeyString = getUncompressedPublicKey(receiverPublicKeyHex);
|
||||
var senderDerivedKey = ellipticCurveEncryption.senderSharedKeyDerivation(
|
||||
receiverPublicKeyString.x, receiverPublicKeyString.y, senderPrivateKey);
|
||||
return senderDerivedKey;
|
||||
}
|
||||
|
||||
function deriveSharedKeyReceiver(senderPublicKeyString, receiverPrivateKey) {
|
||||
return ellipticCurveEncryption.receiverSharedKeyDerivation(
|
||||
senderPublicKeyString.XValuePublicString, senderPublicKeyString.YValuePublicString, receiverPrivateKey);
|
||||
}
|
||||
|
||||
function getReceiverPublicKeyString(privateKey) {
|
||||
return ellipticCurveEncryption.receiverPublicString(privateKey);
|
||||
}
|
||||
|
||||
function wifToDecimal(pk_wif, isPubKeyCompressed = false) {
|
||||
let pk = Bitcoin.Base58.decode(pk_wif)
|
||||
pk.shift()
|
||||
pk.splice(-4, 4)
|
||||
//If the private key corresponded to a compressed public key, also drop the last byte (it should be 0x01).
|
||||
if (isPubKeyCompressed == true) pk.pop()
|
||||
pk.unshift(0)
|
||||
let privateKeyDecimal = BigInteger(pk).toString()
|
||||
let privateKeyHex = Crypto.util.bytesToHex(pk)
|
||||
return {
|
||||
privateKeyDecimal: privateKeyDecimal,
|
||||
privateKeyHex: privateKeyHex
|
||||
}
|
||||
}
|
||||
|
||||
//generate a random Interger within range
|
||||
floCrypto.randInt = function (min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(securedMathRandom() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
//generate a random String within length (options : alphaNumeric chars only)
|
||||
floCrypto.randString = function (length, alphaNumeric = true) {
|
||||
var result = '';
|
||||
var characters = alphaNumeric ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' :
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():';
|
||||
for (var i = 0; i < length; i++)
|
||||
result += characters.charAt(Math.floor(securedMathRandom() * characters.length));
|
||||
return result;
|
||||
}
|
||||
|
||||
//Encrypt Data using public-key
|
||||
floCrypto.encryptData = function (data, receiverPublicKeyHex) {
|
||||
var senderECKeyData = getSenderPublicKeyString();
|
||||
var senderDerivedKey = deriveSharedKeySender(receiverPublicKeyHex, senderECKeyData.privateKey);
|
||||
let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue;
|
||||
let secret = Crypto.AES.encrypt(data, senderKey);
|
||||
return {
|
||||
secret: secret,
|
||||
senderPublicKeyString: senderECKeyData.senderPublicKeyString
|
||||
};
|
||||
}
|
||||
|
||||
//Decrypt Data using private-key
|
||||
floCrypto.decryptData = function (data, privateKeyHex) {
|
||||
var receiverECKeyData = {};
|
||||
if (typeof privateKeyHex !== "string") throw new Error("No private key found.");
|
||||
let privateKey = wifToDecimal(privateKeyHex, true);
|
||||
if (typeof privateKey.privateKeyDecimal !== "string") throw new Error("Failed to detremine your private key.");
|
||||
receiverECKeyData.privateKey = privateKey.privateKeyDecimal;
|
||||
var receiverDerivedKey = deriveSharedKeyReceiver(data.senderPublicKeyString, receiverECKeyData.privateKey);
|
||||
let receiverKey = receiverDerivedKey.XValue + receiverDerivedKey.YValue;
|
||||
let decryptMsg = Crypto.AES.decrypt(data.secret, receiverKey);
|
||||
return decryptMsg;
|
||||
}
|
||||
|
||||
//Sign data using private-key
|
||||
floCrypto.signData = function (data, privateKeyHex) {
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
var messageHash = Crypto.SHA256(data);
|
||||
var messageSign = Bitcoin.ECDSA.sign(messageHash, key.priv);
|
||||
var sighex = Crypto.util.bytesToHex(messageSign);
|
||||
return sighex;
|
||||
}
|
||||
|
||||
//Verify signatue of the data using public-key
|
||||
floCrypto.verifySign = function (data, signatureHex, publicKeyHex) {
|
||||
var msgHash = Crypto.SHA256(data);
|
||||
var sigBytes = Crypto.util.hexToBytes(signatureHex);
|
||||
var publicKeyPoint = ecparams.getCurve().decodePointHex(publicKeyHex);
|
||||
var verify = Bitcoin.ECDSA.verify(msgHash, sigBytes, publicKeyPoint);
|
||||
return verify;
|
||||
}
|
||||
|
||||
//Generates a new flo ID and returns private-key, public-key and floID
|
||||
const generateNewID = floCrypto.generateNewID = function () {
|
||||
var key = new Bitcoin.ECKey(false);
|
||||
key.setCompressed(true);
|
||||
return {
|
||||
floID: key.getBitcoinAddress(),
|
||||
pubKey: key.getPubKeyHex(),
|
||||
privKey: key.getBitcoinWalletImportFormat()
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(floCrypto, {
|
||||
newID: {
|
||||
get: () => generateNewID()
|
||||
},
|
||||
tmpID: {
|
||||
get: () => {
|
||||
let bytes = Crypto.util.randomBytes(20);
|
||||
bytes.unshift(bitjs.pub);
|
||||
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
var checksum = hash.slice(0, 4);
|
||||
return bitjs.Base58.encode(bytes.concat(checksum));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//Returns public-key from private-key
|
||||
floCrypto.getPubKeyHex = function (privateKeyHex) {
|
||||
if (!privateKeyHex)
|
||||
return null;
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return null;
|
||||
key.setCompressed(true);
|
||||
return key.getPubKeyHex();
|
||||
}
|
||||
|
||||
//Returns flo-ID from public-key or private-key
|
||||
floCrypto.getFloID = function (keyHex) {
|
||||
if (!keyHex)
|
||||
return null;
|
||||
try {
|
||||
var key = new Bitcoin.ECKey(keyHex);
|
||||
if (key.priv == null)
|
||||
key.setPub(keyHex);
|
||||
return key.getBitcoinAddress();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.getAddress = function (privateKeyHex, strict = false) {
|
||||
if (!privateKeyHex)
|
||||
return;
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return null;
|
||||
key.setCompressed(true);
|
||||
let pubKey = key.getPubKeyHex(),
|
||||
version = bitjs.Base58.decode(privateKeyHex)[0];
|
||||
switch (version) {
|
||||
case coinjs.priv: //BTC
|
||||
return coinjs.bech32Address(pubKey).address;
|
||||
case bitjs.priv: //FLO
|
||||
return bitjs.pubkey2address(pubKey);
|
||||
default:
|
||||
return strict ? false : bitjs.pubkey2address(pubKey); //default to FLO address (if strict=false)
|
||||
}
|
||||
}
|
||||
|
||||
//Verify the private-key for the given public-key or flo-ID
|
||||
floCrypto.verifyPrivKey = function (privateKeyHex, pubKey_floID, isfloID = true) {
|
||||
if (!privateKeyHex || !pubKey_floID)
|
||||
return false;
|
||||
try {
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return false;
|
||||
key.setCompressed(true);
|
||||
if (isfloID && pubKey_floID == key.getBitcoinAddress())
|
||||
return true;
|
||||
else if (!isfloID && pubKey_floID == key.getPubKeyHex())
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the given flo-id is valid or not
|
||||
floCrypto.validateFloID = function (floID) {
|
||||
if (!floID)
|
||||
return false;
|
||||
try {
|
||||
let addr = new Bitcoin.Address(floID);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the given address (any blockchain) is valid or not
|
||||
floCrypto.validateAddr = function (address, std = true, bech = true) {
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return false;
|
||||
if (typeof raw.version !== 'undefined') { //legacy or segwit
|
||||
if (std == false)
|
||||
return false;
|
||||
else if (std === true || (!Array.isArray(std) && std === raw.version) || (Array.isArray(std) && std.includes(raw.version)))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} else if (typeof raw.bech_version !== 'undefined') { //bech32
|
||||
if (bech === false)
|
||||
return false;
|
||||
else if (bech === true || (!Array.isArray(bech) && bech === raw.bech_version) || (Array.isArray(bech) && bech.includes(raw.bech_version)))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} else //unknown
|
||||
return false;
|
||||
}
|
||||
|
||||
//Check the public-key for the address (any blockchain)
|
||||
floCrypto.verifyPubKey = function (pubKeyHex, address) {
|
||||
let raw = decodeAddress(address),
|
||||
pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), {
|
||||
asBytes: true
|
||||
})));
|
||||
return raw ? pub_hash === raw.hex : false;
|
||||
}
|
||||
|
||||
//Convert the given address (any blockchain) to equivalent floID
|
||||
floCrypto.toFloID = function (address, options = null) {
|
||||
if (!address)
|
||||
return;
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return;
|
||||
else if (options) {
|
||||
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
|
||||
return;
|
||||
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
|
||||
return;
|
||||
}
|
||||
raw.bytes.unshift(bitjs.pub);
|
||||
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
|
||||
}
|
||||
|
||||
//Checks if the given addresses (any blockchain) are same (w.r.t keys)
|
||||
floCrypto.isSameAddr = function (addr1, addr2) {
|
||||
if (!addr1 || !addr2)
|
||||
return;
|
||||
let raw1 = decodeAddress(addr1),
|
||||
raw2 = decodeAddress(addr2);
|
||||
if (!raw1 || !raw2)
|
||||
return false;
|
||||
else
|
||||
return raw1.hex === raw2.hex;
|
||||
}
|
||||
|
||||
const decodeAddress = floCrypto.decodeAddr = function (address) {
|
||||
if (!address)
|
||||
return;
|
||||
else if (address.length == 33 || address.length == 34) { //legacy encoding
|
||||
let decode = bitjs.Base58.decode(address);
|
||||
let bytes = decode.slice(0, decode.length - 4);
|
||||
let checksum = decode.slice(decode.length - 4),
|
||||
hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3]) ? null : {
|
||||
version: bytes.shift(),
|
||||
hex: Crypto.util.bytesToHex(bytes),
|
||||
bytes
|
||||
}
|
||||
} else if (address.length == 42) { //bech encoding
|
||||
let decode = coinjs.bech32_decode(address);
|
||||
if (decode) {
|
||||
let bytes = decode.data;
|
||||
let bech_version = bytes.shift();
|
||||
bytes = coinjs.bech32_convert(bytes, 5, 8, false);
|
||||
return {
|
||||
bech_version,
|
||||
hrp: decode.hrp,
|
||||
hex: Crypto.util.bytesToHex(bytes),
|
||||
bytes
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//Split the str using shamir's Secret and Returns the shares
|
||||
floCrypto.createShamirsSecretShares = function (str, total_shares, threshold_limit) {
|
||||
try {
|
||||
if (str.length > 0) {
|
||||
var strHex = shamirSecretShare.str2hex(str);
|
||||
var shares = shamirSecretShare.share(strHex, total_shares, threshold_limit);
|
||||
return shares;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//Returns the retrived secret by combining the shamirs shares
|
||||
const retrieveShamirSecret = floCrypto.retrieveShamirSecret = function (sharesArray) {
|
||||
try {
|
||||
if (sharesArray.length > 0) {
|
||||
var comb = shamirSecretShare.combine(sharesArray.slice(0, sharesArray.length));
|
||||
comb = shamirSecretShare.hex2str(comb);
|
||||
return comb;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//Verifies the shares and str
|
||||
floCrypto.verifyShamirsSecret = function (sharesArray, str) {
|
||||
if (!str)
|
||||
return null;
|
||||
else if (retrieveShamirSecret(sharesArray) === str)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
const validateASCII = floCrypto.validateASCII = function (string, bool = true) {
|
||||
if (typeof string !== "string")
|
||||
return null;
|
||||
if (bool) {
|
||||
let x;
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
x = string.charCodeAt(i);
|
||||
if (x < 32 || x > 127)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
let x, invalids = {};
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
x = string.charCodeAt(i);
|
||||
if (x < 32 || x > 127)
|
||||
if (x in invalids)
|
||||
invalids[string[i]].push(i)
|
||||
else
|
||||
invalids[string[i]] = [i];
|
||||
}
|
||||
if (Object.keys(invalids).length)
|
||||
return invalids;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.convertToASCII = function (string, mode = 'soft-remove') {
|
||||
let chars = validateASCII(string, false);
|
||||
if (chars === true)
|
||||
return string;
|
||||
else if (chars === null)
|
||||
return null;
|
||||
let convertor, result = string,
|
||||
refAlt = {};
|
||||
ascii_alternatives.split('\n').forEach(a => refAlt[a[0]] = a.slice(2));
|
||||
mode = mode.toLowerCase();
|
||||
if (mode === "hard-unicode")
|
||||
convertor = (c) => `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
|
||||
else if (mode === "soft-unicode")
|
||||
convertor = (c) => refAlt[c] || `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
|
||||
else if (mode === "hard-remove")
|
||||
convertor = c => "";
|
||||
else if (mode === "soft-remove")
|
||||
convertor = c => refAlt[c] || "";
|
||||
else
|
||||
return null;
|
||||
for (let c in chars)
|
||||
result = result.replaceAll(c, convertor(c));
|
||||
return result;
|
||||
}
|
||||
|
||||
floCrypto.revertUnicode = function (string) {
|
||||
return string.replace(/\\u[\dA-F]{4}/gi,
|
||||
m => String.fromCharCode(parseInt(m.replace(/\\u/g, ''), 16)));
|
||||
}
|
||||
|
||||
})('object' === typeof module ? module.exports : window.floCrypto = {});
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,19 +1,7 @@
|
||||
const floGlobals = {
|
||||
|
||||
//Required for all
|
||||
blockchain: "FLO",
|
||||
|
||||
//Required for blockchain API operators
|
||||
apiURL: {
|
||||
FLO: ['https://livenet.flocha.in/', 'https://flosight.duckdns.org/'],
|
||||
FLO_TEST: ['https://testnet-flosight.duckdns.org', 'https://testnet.flocha.in/']
|
||||
},
|
||||
application: "exchange",
|
||||
adminID: "FMxYC7gYZhouzqtHZukGnPiQ8nvG4CMzXM",
|
||||
sendAmt: 0.001,
|
||||
fee: 0.0005,
|
||||
tokenURL: "https://ranchimallflo.duckdns.org/",
|
||||
marketID: "FMxYC7gYZhouzqtHZukGnPiQ8nvG4CMzXM",
|
||||
currency: "rupee"
|
||||
};
|
||||
|
||||
('object' === typeof module) ? module.exports = floGlobals: null;
|
||||
('object' === typeof module) ? module.exports = floGlobals : null;
|
||||
@ -1,53 +1,102 @@
|
||||
'use strict';
|
||||
(function(EXPORTS) { //floTokenAPI v1.0.3c
|
||||
/* Token Operator to send/receive tokens via blockchain using API calls*/
|
||||
'use strict';
|
||||
const tokenAPI = EXPORTS;
|
||||
|
||||
/* Token Operator to send/receive tokens from blockchain using API calls*/
|
||||
(function(GLOBAL) {
|
||||
const floTokenAPI = GLOBAL.floTokenAPI = {
|
||||
fetch_api: function(apicall) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(floGlobals.tokenURL + apicall);
|
||||
fetch(floGlobals.tokenURL + apicall).then(response => {
|
||||
if (response.ok)
|
||||
response.json().then(data => resolve(data));
|
||||
else
|
||||
reject(response)
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
},
|
||||
getBalance: function(floID, token = floGlobals.currency) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.fetch_api(`api/v1.0/getFloAddressBalance?token=${token}&floAddress=${floID}`)
|
||||
.then(result => resolve(result.balance || 0))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
},
|
||||
getTx: function(txID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.fetch_api(`api/v1.0/getTransactionDetails/${txID}`).then(res => {
|
||||
if (res.result === "error")
|
||||
reject(res.description);
|
||||
else if (!res.parsedFloData)
|
||||
reject("Data piece (parsedFloData) missing");
|
||||
else if (!res.transactionDetails)
|
||||
reject("Data piece (transactionDetails) missing");
|
||||
else
|
||||
resolve(res);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
},
|
||||
sendToken: function(privKey, amount, receiverID, message = "", token = floGlobals.currency) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let senderID = floCrypto.getFloID(privKey);
|
||||
if (typeof amount !== "number" || amount <= 0)
|
||||
return reject("Invalid amount");
|
||||
this.getBalance(senderID, token).then(bal => {
|
||||
if (amount > bal)
|
||||
return reject("Insufficiant token balance");
|
||||
floBlockchainAPI.writeData(senderID, `send ${amount} ${token}# ${message}`, privKey, receiverID)
|
||||
.then(txid => resolve(txid))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
const DEFAULT = {
|
||||
apiURL: floGlobals.tokenURL || "https://ranchimallflo.duckdns.org/",
|
||||
currency: "rupee"
|
||||
}
|
||||
})(typeof global !== "undefined" ? global : window);
|
||||
|
||||
Object.defineProperties(tokenAPI, {
|
||||
URL: {
|
||||
get: () => DEFAULT.apiURL
|
||||
},
|
||||
currency: {
|
||||
get: () => DEFAULT.currency,
|
||||
set: currency => DEFAULT.currency = currency
|
||||
}
|
||||
});
|
||||
|
||||
if (floGlobals.currency) tokenAPI.currency = floGlobals.currency;
|
||||
|
||||
Object.defineProperties(floGlobals, {
|
||||
currency: {
|
||||
get: () => DEFAULT.currency,
|
||||
set: currency => DEFAULT.currency = currency
|
||||
}
|
||||
});
|
||||
|
||||
const fetch_api = tokenAPI.fetch = function(apicall) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.debug(DEFAULT.apiURL + apicall);
|
||||
fetch(DEFAULT.apiURL + apicall).then(response => {
|
||||
if (response.ok)
|
||||
response.json().then(data => resolve(data));
|
||||
else
|
||||
reject(response)
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
const getBalance = tokenAPI.getBalance = function(floID, token = DEFAULT.currency) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch_api(`api/v1.0/getFloAddressBalance?token=${token}&floAddress=${floID}`)
|
||||
.then(result => resolve(result.balance || 0))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
tokenAPI.getTx = function(txID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch_api(`api/v1.0/getTransactionDetails/${txID}`).then(res => {
|
||||
if (res.result === "error")
|
||||
reject(res.description);
|
||||
else if (!res.parsedFloData)
|
||||
reject("Data piece (parsedFloData) missing");
|
||||
else if (!res.transactionDetails)
|
||||
reject("Data piece (transactionDetails) missing");
|
||||
else
|
||||
resolve(res);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
tokenAPI.sendToken = function(privKey, amount, receiverID, message = "", token = DEFAULT.currency, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let senderID = floCrypto.getFloID(privKey);
|
||||
if (typeof amount !== "number" || isNaN(amount) || amount <= 0)
|
||||
return reject("Invalid amount");
|
||||
getBalance(senderID, token).then(bal => {
|
||||
if (amount > bal)
|
||||
return reject(`Insufficient ${token}# balance`);
|
||||
floBlockchainAPI.writeData(senderID, `send ${amount} ${token}# ${message}`, privKey, receiverID, options)
|
||||
.then(txid => resolve(txid))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
|
||||
tokenAPI.getAllTxs = function(floID, token = DEFAULT.currency) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch_api(`api/v1.0/getFloAddressTransactions?token=${token}&floAddress=${floID}`)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
const util = tokenAPI.util = {};
|
||||
|
||||
util.parseTxData = function(txData) {
|
||||
let parsedData = {};
|
||||
for (let p in txData.parsedFloData)
|
||||
parsedData[p] = txData.parsedFloData[p];
|
||||
parsedData.sender = txData.transactionDetails.vin[0].addr;
|
||||
for (let vout of txData.transactionDetails.vout)
|
||||
if (vout.scriptPubKey.addresses[0] !== parsedData.sender)
|
||||
parsedData.receiver = vout.scriptPubKey.addresses[0];
|
||||
parsedData.time = txData.transactionDetails.time;
|
||||
return parsedData;
|
||||
}
|
||||
|
||||
})('object' === typeof module ? module.exports : window.floTokenAPI = {});
|
||||
15145
docs/scripts/lib.js
15145
docs/scripts/lib.js
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@
|
||||
"express-session": "^1.17.2",
|
||||
"mysql": "^2.18.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
"proper-lockfile": "^4.1.2",
|
||||
"ws": "^8.2.3"
|
||||
},
|
||||
"devDependencies": {},
|
||||
|
||||
@ -1,9 +1,17 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const getInput = require('./getInput');
|
||||
|
||||
let _I = "";
|
||||
for (let arg of process.argv)
|
||||
if (/^-I=/.test(arg)) {
|
||||
_I = arg.split(/=(.*)/s)[1];
|
||||
break;
|
||||
}
|
||||
|
||||
var config, flag_new;
|
||||
try {
|
||||
config = require(`../args/config${process.env.I || ""}.json`);
|
||||
config = require(`../args/config${_I}.json`);
|
||||
flag_new = false;
|
||||
} catch (error) {
|
||||
config = {
|
||||
@ -24,8 +32,8 @@ function flaggedYesOrNo(text) {
|
||||
resolve(true);
|
||||
else
|
||||
getInput.YesOrNo(text)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
@ -84,7 +92,7 @@ function configure() {
|
||||
configurePort().then(port_result => {
|
||||
randomizeSessionSecret().then(secret_result => {
|
||||
configureSQL().then(sql_result => {
|
||||
fs.writeFile(__dirname + `/../args/config${process.env.I || ""}.json`, JSON.stringify(config), 'utf8', (err) => {
|
||||
fs.writeFile(path.resolve(__dirname, '..', 'args', `config${_I}.json`), JSON.stringify(config), 'utf8', (err) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return reject(false);
|
||||
@ -114,6 +122,6 @@ function configure() {
|
||||
}
|
||||
|
||||
if (!module.parent)
|
||||
configure().then(_ => null).catch(error => console.error(error));
|
||||
configure().then(_ => null).catch(error => console.error(error)).finally(_ => process.exit(0));
|
||||
else
|
||||
module.exports = configure;
|
||||
@ -1,15 +1,23 @@
|
||||
const fs = require('fs');
|
||||
let Database = require('../src/database');
|
||||
const path = require('path');
|
||||
let DB = require('../src/database');
|
||||
|
||||
let _I = "";
|
||||
for (let arg of process.argv)
|
||||
if (/^-I=/.test(arg)) {
|
||||
_I = arg.split(/=(.*)/s)[1];
|
||||
break;
|
||||
}
|
||||
|
||||
function createSchema() {
|
||||
const config = require(`../args/config${process.env.I || ""}.json`);
|
||||
const config = require(`../args/config${_I}.json`);
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(__dirname + '/../args/schema.sql', 'utf8', (err, data) => {
|
||||
fs.readFile(path.resolve(__dirname, '..', 'args', `schema.sql`), 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return reject(null);
|
||||
}
|
||||
Database(config["sql_user"], config["sql_pwd"], config["sql_db"], config["sql_host"]).then(DB => {
|
||||
DB.connect(config["sql_user"], config["sql_pwd"], config["sql_db"], config["sql_host"]).then(_ => {
|
||||
let txQueries = data.split(';');
|
||||
txQueries.pop();
|
||||
txQueries = txQueries.map(q => q.trim().replace(/\n/g, ' '));
|
||||
@ -32,6 +40,6 @@ function createSchema() {
|
||||
}
|
||||
|
||||
if (!module.parent)
|
||||
createSchema().then(_ => null).catch(_ => null);
|
||||
createSchema().then(_ => null).catch(_ => null).finally(_ => process.exit(0));
|
||||
else
|
||||
module.exports = createSchema;
|
||||
@ -8,19 +8,17 @@ npm run setup - Finish the setup (configure and reset password
|
||||
npm run configure - Configure the app.
|
||||
npm run reset-password - Reset the password (for private-key).
|
||||
npm run create-schema - Create schema in MySQL database.
|
||||
npm run configure-backup - Configure the backup.
|
||||
npm run create-backup-schema - Create backup-schema in MySQL database.
|
||||
npm run backup - Run the backup-node.
|
||||
|
||||
npm start - Start the application (main).
|
||||
|
||||
NOTE: env variable 'PASSWORD' required for 'npm start'.
|
||||
NOTE: argument 'PASSWORD' required for 'npm start'
|
||||
npm start -- -PASSWORD=<password>
|
||||
|
||||
WINDOWS:
|
||||
$env:PASSWORD="<password>"; npm start
|
||||
(Optional) Multiple instance can be run/setup on the same dir with different config files by using argument 'I'.
|
||||
<command> -- -I=<instance_ID>
|
||||
|
||||
LINUX:
|
||||
PASSWORD="<password"> npm start
|
||||
(Optional) 'console.debug' is now turned off by default. pass argument '--debug' to turn it on
|
||||
npm start -- -PASSWORD=<password> --debug
|
||||
`;
|
||||
|
||||
console.log(message);
|
||||
@ -23,13 +23,16 @@ getInput.YesOrNo('Do you want to finish the setup now').then(value => {
|
||||
.catch(_ => console.log('Reset the password later using: '))
|
||||
.finally(_ => {
|
||||
console.log('npm run reset-password');
|
||||
process.exit(0);
|
||||
})
|
||||
} else
|
||||
} else {
|
||||
console.log('Reset the password later using:\n' + 'npm run reset-password');
|
||||
process.exit(0);
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
console.log('Finish the setup later using:\n' + 'npm run setup');
|
||||
console.log('To configure for backup use:\n' + 'npm run configure-backup');
|
||||
process.exit(0);
|
||||
}
|
||||
})
|
||||
@ -1,12 +1,18 @@
|
||||
const fs = require('fs');
|
||||
const getInput = require('./getInput');
|
||||
|
||||
const floGlobals = require('../docs/scripts/floGlobals');
|
||||
global.floGlobals = require('../docs/scripts/floGlobals');
|
||||
require('../src/set_globals');
|
||||
require('../docs/scripts/lib');
|
||||
require('../docs/scripts/floCrypto');
|
||||
const floCrypto = require('../docs/scripts/floCrypto');
|
||||
const path = require('path');
|
||||
|
||||
console.log(__dirname);
|
||||
let _I = "";
|
||||
for (let arg of process.argv)
|
||||
if (/^-I=/.test(arg)) {
|
||||
_I = arg.split(/=(.*)/s)[1];
|
||||
break;
|
||||
}
|
||||
|
||||
function validateKey(privKey) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -62,7 +68,7 @@ function resetPassword() {
|
||||
let encrypted = Crypto.AES.encrypt(privKey, password);
|
||||
let randNum = floCrypto.randInt(10, 15);
|
||||
let splitShares = floCrypto.createShamirsSecretShares(encrypted, randNum, randNum);
|
||||
fs.writeFile(__dirname + `/../args/keys${process.env.I || ""}.json`, JSON.stringify(splitShares), 'utf8', (err) => {
|
||||
fs.writeFile(path.resolve(__dirname, '..', 'args', `keys${_I}.json`), JSON.stringify(splitShares), 'utf8', (err) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return reject(false);
|
||||
@ -83,6 +89,6 @@ function resetPassword() {
|
||||
}
|
||||
|
||||
if (!module.parent)
|
||||
resetPassword().then(_ => null).catch(error => console.error(error));
|
||||
resetPassword().then(_ => null).catch(_ => null).finally(_ => process.exit(0));
|
||||
else
|
||||
module.exports = resetPassword;
|
||||
@ -1,33 +1,51 @@
|
||||
module.exports = {
|
||||
app: {
|
||||
BLOCKCHAIN_REFRESH_INTERVAL: 1 * 60 * 60 * 1000, // 1 hr
|
||||
PERIOD_INTERVAL: 15 * 60 * 1000 // 15 min
|
||||
BLOCKCHAIN_REFRESH_INTERVAL: 1 * 60 * 60 * 1000, //1 hr
|
||||
},
|
||||
request: {
|
||||
SIGN_EXPIRE_TIME: 1 * 60 * 1000, //1 min
|
||||
SIGN_EXPIRE_TIME: 5 * 60 * 1000, //5 mins
|
||||
MAX_SESSION_TIMEOUT: 30 * 24 * 60 * 60 * 1000, //30 days
|
||||
INVALID_SERVER_MSG: "INCORRECT_SERVER_ERROR" //Should be reflected in public backend script
|
||||
},
|
||||
background: {
|
||||
PERIOD_INTERVAL: 5 * 60 * 1000, //5 min,
|
||||
WAIT_TIME: 2 * 60 * 1000, //2 mins,
|
||||
REQUEST_TIMEOUT: 24 * 60 * 60 * 1000, //1 day
|
||||
},
|
||||
keys: {
|
||||
SHARES_PER_NODE: 8,
|
||||
SHARE_THRESHOLD: 50 / 100, //50%
|
||||
DISCARD_COOLDOWN: 24 * 60 * 60 * 1000, //1 day
|
||||
SHUFFLE_INTERVAL: 12 * 60 * 60 * 1000, //12 hrs
|
||||
},
|
||||
market: {
|
||||
MINIMUM_BUY_REQUIREMENT: 0,
|
||||
LAUNCH_SELLER_TAG: "launch-seller",
|
||||
MAXIMUM_LAUNCH_SELL_CHIPS: 100000,
|
||||
TRADE_HASH_PREFIX: "z1",
|
||||
TRANSFER_HASH_PREFIX: "z0"
|
||||
},
|
||||
price: {
|
||||
MIN_TIME: 1 * 60 * 60 * 1000, // 1 hr
|
||||
DOWN_RATE: 0.2 / 100, //0.2% dec
|
||||
UP_RATE: 0.5 / 100, //0.5 % inc
|
||||
MAX_DOWN_PER_DAY: 4.8 / 100, //max 4.8% dec
|
||||
MAX_UP_PER_DAY: 12 / 100, //max 12% inc
|
||||
DOWN_RATE: 0.05 / 100, //0.05% dec
|
||||
UP_RATE: 0.2 / 100, //0.2% inc
|
||||
MAX_DOWN_PER_DAY: 1 / 100, //max 1% dec
|
||||
MAX_UP_PER_DAY: 4 / 100, //max 4% inc
|
||||
CHECK_RATED_SELLER: false,
|
||||
TOP_RANGE: 10 / 100, //top 10%
|
||||
REC_HISTORY_INTERVAL: 1 * 60 * 60 * 1000, // 1 hr
|
||||
REC_HISTORY_INTERVAL: 1 * 60 * 60 * 1000, //1 hr
|
||||
},
|
||||
convert: {
|
||||
MIN_FUND: 0.3, // 30%
|
||||
TO_FIXED_VALUES: [250, 500],
|
||||
TO_MIN_VALUE: 1000,
|
||||
TO_MAX_VALUE: 10000,
|
||||
FROM_FIXED_VALUES: [0.01],
|
||||
FROM_MIN_VALUE: 0.0001,
|
||||
FROM_MAX_VALUE: 10000,
|
||||
},
|
||||
backup: {
|
||||
SHARE_THRESHOLD: 50 / 100, // 50%
|
||||
HASH_N_ROW: 100,
|
||||
SINK_KEY_INDICATOR: '$$$',
|
||||
BACKUP_INTERVAL: 5 * 60 * 1000, //5 min
|
||||
BACKUP_SYNC_TIMEOUT: 10 * 60 * 1000, //10 mins
|
||||
CHECKSUM_INTERVAL: 100, //times of BACKUP_INTERVAL
|
||||
CHECKSUM_INTERVAL: 10, //times of BACKUP_INTERVAL
|
||||
}
|
||||
}
|
||||
65
src/app.js
65
src/app.js
@ -3,15 +3,13 @@ const express = require('express');
|
||||
//const cookieParser = require("cookie-parser");
|
||||
//const sessions = require('express-session');
|
||||
const Request = require('./request');
|
||||
const path = require('path');
|
||||
const PUBLIC_DIR = path.resolve(__dirname, '..', 'docs');
|
||||
|
||||
const {
|
||||
PERIOD_INTERVAL
|
||||
} = require("./_constants")["app"];
|
||||
|
||||
module.exports = function App(secret, DB) {
|
||||
module.exports = function App(secret) {
|
||||
|
||||
if (!(this instanceof App))
|
||||
return new App(secret, DB);
|
||||
return new App(secret);
|
||||
|
||||
var server = null;
|
||||
const app = express();
|
||||
@ -39,7 +37,7 @@ module.exports = function App(secret, DB) {
|
||||
}));
|
||||
*/
|
||||
|
||||
app.use(function(req, res, next) {
|
||||
app.use(function (req, res, next) {
|
||||
res.setHeader('Access-Control-Allow-Origin', "*");
|
||||
// Request methods you wish to allow
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
|
||||
@ -75,8 +73,10 @@ module.exports = function App(secret, DB) {
|
||||
|
||||
//get rates, balance and tx
|
||||
app.get('/get-rates', Request.GetRates);
|
||||
app.get('/rate-history', Request.GetRateHistory);
|
||||
app.get('/get-balance', Request.GetBalance);
|
||||
app.get('/get-transaction', Request.GetTransaction);
|
||||
app.get('/get-sink', Request.GetSink);
|
||||
|
||||
//get account details
|
||||
app.post('/account', Request.Account);
|
||||
@ -86,16 +86,39 @@ module.exports = function App(secret, DB) {
|
||||
app.post('/withdraw-flo', Request.WithdrawFLO);
|
||||
app.post('/deposit-token', Request.DepositToken);
|
||||
app.post('/withdraw-token', Request.WithdrawToken);
|
||||
app.post('/get-transact', Request.GetUserTransacts);
|
||||
|
||||
//Manage user tags (Access to trusted IDs only)
|
||||
//generate or discard sinks (admin only)
|
||||
app.post('/generate-sink', Request.GenerateSink);
|
||||
app.post('/reshare-sink', Request.ReshareSink);
|
||||
app.post('/discard-sink', Request.DiscardSink);
|
||||
|
||||
//convert from or to coin
|
||||
app.get('/get-convert-values', Request.GetConvertValues);
|
||||
app.post('/convert-to', Request.ConvertTo);
|
||||
app.post('/convert-from', Request.ConvertFrom);
|
||||
app.post('/deposit-convert-coin-fund', Request.DepositConvertCoinFund);
|
||||
app.post('/deposit-convert-currency-fund', Request.DepositConvertCurrencyFund);
|
||||
app.post('/withdraw-convert-coin-fund', Request.WithdrawConvertCoinFund);
|
||||
app.post('/withdraw-convert-currency-fund', Request.WithdrawConvertCurrencyFund);
|
||||
|
||||
//close blockchain-bond and bobs-fund-investment
|
||||
app.post('/close-blockchain-bonds', Request.CloseBlockchainBond);
|
||||
app.post('/close-bobs-fund-investment', Request.CloseBobsFund);
|
||||
|
||||
//check balance for blockchain-bond and bobs-fund (trusted IDs only)
|
||||
app.post('/check-blockchain-bond', Request.CheckBlockchainBondBalance);
|
||||
app.post('/check-bobs-fund', Request.CheckBobsFundBalance);
|
||||
|
||||
//Manage user tags (trusted IDs only)
|
||||
app.post('/add-tag', Request.AddUserTag);
|
||||
app.post('/remove-tag', Request.RemoveUserTag);
|
||||
app.post('/add-distributor', Request.AddDistributor);
|
||||
app.post('/remove-distributor', Request.RemoveDistributor);
|
||||
|
||||
Request.DB = DB;
|
||||
Request.secret = secret;
|
||||
|
||||
//Properties
|
||||
var periodInstance = null;
|
||||
let self = this;
|
||||
|
||||
//return server, express-app
|
||||
@ -115,6 +138,9 @@ module.exports = function App(secret, DB) {
|
||||
set: (assets) => Request.assetList = assets
|
||||
});
|
||||
|
||||
//Refresh data (from blockchain)
|
||||
self.refreshData = (nodeList) => Request.refreshData(nodeList);
|
||||
|
||||
//Start (or) Stop servers
|
||||
self.start = (port) => new Promise(resolve => {
|
||||
server = app.listen(port, () => {
|
||||
@ -129,23 +155,8 @@ module.exports = function App(secret, DB) {
|
||||
});
|
||||
|
||||
//(Node is not master) Pause serving the clients
|
||||
self.pause = () => {
|
||||
Request.pause();
|
||||
if (periodInstance !== null) {
|
||||
clearInterval(periodInstance);
|
||||
periodInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
self.pause = () => Request.pause();
|
||||
//(Node is master) Resume serving the clients
|
||||
self.resume = () => {
|
||||
Request.resume();
|
||||
Request.periodicProcess();
|
||||
if (periodInstance === null)
|
||||
periodInstance = setInterval(Request.periodicProcess, PERIOD_INTERVAL);
|
||||
}
|
||||
self.resume = () => Request.resume();
|
||||
|
||||
Object.defineProperty(self, "periodInstance", {
|
||||
get: () => periodInstance
|
||||
});
|
||||
}
|
||||
541
src/background.js
Normal file
541
src/background.js
Normal file
@ -0,0 +1,541 @@
|
||||
'use strict';
|
||||
|
||||
const keys = require('./keys');
|
||||
const blockchain = require('./blockchain');
|
||||
const conversion_rates = require('./services/conversion').getRate;
|
||||
const bond_util = require('./services/bonds').util;
|
||||
const fund_util = require('./services/bobs-fund').util;
|
||||
const pCode = require('../docs/scripts/floExchangeAPI').processCode;
|
||||
const DB = require("./database");
|
||||
const coupling = require('./coupling');
|
||||
const price = require('./price');
|
||||
|
||||
const {
|
||||
PERIOD_INTERVAL,
|
||||
REQUEST_TIMEOUT
|
||||
} = require('./_constants')['background'];
|
||||
const {
|
||||
LAUNCH_SELLER_TAG,
|
||||
MAXIMUM_LAUNCH_SELL_CHIPS
|
||||
} = require('./_constants')["market"];
|
||||
|
||||
var assetList; //container and allowed assets
|
||||
var updateBalance; // container for updateBalance function
|
||||
|
||||
const verifyTx = {};
|
||||
|
||||
function confirmDepositFLO() {
|
||||
DB.query("SELECT id, floID, txid FROM VaultTransactions WHERE mode=? AND asset=? AND asset_type=? AND r_status=?", [pCode.VAULT_MODE_DEPOSIT, "FLO", pCode.ASSET_TYPE_COIN, pCode.STATUS_PENDING]).then(results => {
|
||||
results.forEach(r => {
|
||||
verifyTx.FLO(r.floID, r.txid, keys.sink_groups.EXCHANGE).then(amount => {
|
||||
addSellChipsIfLaunchSeller(r.floID, amount).then(txQueries => {
|
||||
txQueries.push(updateBalance.add(r.floID, "FLO", amount));
|
||||
txQueries.push(["UPDATE VaultTransactions SET r_status=?, amount=? WHERE id=?", [pCode.STATUS_SUCCESS, amount, r.id]]);
|
||||
DB.transaction(txQueries)
|
||||
.then(result => console.info("FLO deposited:", r.floID, amount))
|
||||
.catch(error => console.error(error))
|
||||
}).catch(error => console.error(error))
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
if (error[0])
|
||||
DB.query("UPDATE VaultTransactions SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id])
|
||||
.then(_ => null).catch(error => console.error(error));
|
||||
});
|
||||
})
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
verifyTx.FLO = function (sender, txid, group) {
|
||||
return new Promise((resolve, reject) => {
|
||||
floBlockchainAPI.getTx(txid).then(tx => {
|
||||
let vin_sender = tx.vin.filter(v => v.addr === sender)
|
||||
if (!vin_sender.length)
|
||||
return reject([true, "Transaction not sent by the sender"]);
|
||||
if (vin_sender.length !== tx.vin.length)
|
||||
return reject([true, "Transaction input containes other floIDs"]);
|
||||
if (!tx.blockheight)
|
||||
return reject([false, "Transaction not included in any block yet"]);
|
||||
if (!tx.confirmations)
|
||||
return reject([false, "Transaction not confirmed yet"]);
|
||||
let amount = tx.vout.reduce((a, v) => keys.sink_chest.includes(group, v.scriptPubKey.addresses[0]) ? a + v.value : a, 0);
|
||||
if (amount == 0)
|
||||
return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes)
|
||||
else
|
||||
resolve(amount);
|
||||
}).catch(error => reject([false, error]))
|
||||
})
|
||||
}
|
||||
|
||||
function checkTag(floID, tag) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT id FROM UserTag WHERE floID=? AND tag=?", [floID, tag])
|
||||
.then(result => resolve(result.length ? true : false))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function addSellChipsIfLaunchSeller(floID, quantity) {
|
||||
return new Promise((resolve, reject) => {
|
||||
checkTag(floID, LAUNCH_SELLER_TAG).then(result => {
|
||||
if (result) //floID is launch-seller
|
||||
Promise.all([
|
||||
DB.query("SELECT IFNULL(SUM(quantity), 0) AS sold FROM TradeTransactions WHERE seller=? AND asset=?", [floID, 'FLO']),
|
||||
DB.query("SELECT IFNULL(SUM(quantity), 0) AS brought FROM TradeTransactions WHERE buyer=? AND asset=?", [floID, 'FLO']),
|
||||
DB.query("SELECT IFNULL(SUM(quantity), 0) AS chips FROM SellChips WHERE floID=? AND asset=?", [floID, 'FLO']),
|
||||
]).then(result => {
|
||||
let sold = result[0][0].sold,
|
||||
brought = result[1][0].brought,
|
||||
chips = result[2][0].chips;
|
||||
let remLaunchChips = MAXIMUM_LAUNCH_SELL_CHIPS - (sold + chips) + brought;
|
||||
quantity = Math.min(quantity, remLaunchChips);
|
||||
if (quantity > 0)
|
||||
resolve([["INSERT INTO SellChips(floID, asset, quantity) VALUES (?)", [[floID, 'FLO', quantity]]]]);
|
||||
else
|
||||
resolve([]);
|
||||
}).catch(error => reject(error))
|
||||
else //floID is not launch-seller
|
||||
resolve([]);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function confirmDepositToken() {
|
||||
DB.query("SELECT id, floID, txid FROM VaultTransactions WHERE mode=? AND asset_type=? AND r_status=?", [pCode.VAULT_MODE_DEPOSIT, pCode.ASSET_TYPE_TOKEN, pCode.STATUS_PENDING]).then(results => {
|
||||
results.forEach(r => {
|
||||
verifyTx.token(r.floID, r.txid, keys.sink_groups.EXCHANGE).then(({ token, amount, flo_amount }) => {
|
||||
DB.query("SELECT id FROM VaultTransactions where floID=? AND mode=? AND asset=? AND asset_type=? AND txid=?", [r.floID, pCode.VAULT_MODE_DEPOSIT, "FLO", pCode.ASSET_TYPE_COIN, r.txid]).then(result => {
|
||||
let txQueries = [];
|
||||
//Add the FLO balance if necessary
|
||||
if (!result.length) {
|
||||
txQueries.push(updateBalance.add(r.floID, "FLO", flo_amount));
|
||||
txQueries.push(["INSERT INTO VaultTransactions(txid, floID, mode, asset_type, asset, amount, r_status) VALUES (?)", [[r.txid, r.floID, pCode.VAULT_MODE_DEPOSIT, pCode.ASSET_TYPE_COIN, "FLO", flo_amount, pCode.STATUS_SUCCESS]]]);
|
||||
}
|
||||
txQueries.push(["UPDATE VaultTransactions SET r_status=?, asset=?, amount=? WHERE id=?", [pCode.STATUS_SUCCESS, token, amount, r.id]]);
|
||||
txQueries.push(updateBalance.add(r.floID, token, amount));
|
||||
DB.transaction(txQueries)
|
||||
.then(result => console.info("Token deposited:", r.floID, token, amount))
|
||||
.catch(error => console.error(error));
|
||||
}).catch(error => console.error(error));
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
if (error[0])
|
||||
DB.query("UPDATE VaultTransactions SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id])
|
||||
.then(_ => null).catch(error => console.error(error));
|
||||
});
|
||||
})
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
verifyTx.token = function (sender, txid, group, currencyOnly = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
floTokenAPI.getTx(txid).then(tx => {
|
||||
if (tx.parsedFloData.type !== "transfer")
|
||||
return reject([true, "Transaction type not 'transfer'"]);
|
||||
else if (tx.parsedFloData.transferType !== "token")
|
||||
return reject([true, "Transaction transfer is not 'token'"]);
|
||||
var token = tx.parsedFloData.tokenIdentification,
|
||||
amount = tx.parsedFloData.tokenAmount;
|
||||
if (currencyOnly && token !== floGlobals.currency)
|
||||
return reject([true, "Token not currency"]);
|
||||
else if (!currencyOnly && ((!assetList.includes(token) && token !== floGlobals.currency) || token === "FLO"))
|
||||
return reject([true, "Token not authorised"]);
|
||||
let vin_sender = tx.transactionDetails.vin.filter(v => v.addr === sender)
|
||||
if (!vin_sender.length)
|
||||
return reject([true, "Transaction not sent by the sender"]);
|
||||
let flo_amount = tx.transactionDetails.vout.reduce((a, v) => keys.sink_chest.includes(group, v.scriptPubKey.addresses[0]) ? a + v.value : a, 0);
|
||||
if (flo_amount == 0)
|
||||
return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes)
|
||||
else
|
||||
resolve({ token, amount, flo_amount });
|
||||
}).catch(error => reject([false, error]))
|
||||
})
|
||||
}
|
||||
|
||||
function retryVaultWithdrawal() {
|
||||
DB.query("SELECT id, floID, asset, asset_type, amount FROM VaultTransactions WHERE mode=? AND r_status=?", [pCode.VAULT_MODE_WITHDRAW, pCode.STATUS_PENDING]).then(results => {
|
||||
results.forEach(r => {
|
||||
if (r.asset_type == pCode.ASSET_TYPE_COIN) {
|
||||
if (r.asset == "FLO")
|
||||
blockchain.withdrawAsset.retry(r.floID, r.asset, r.amount, r.id);
|
||||
} else if (r.asset_type == pCode.ASSET_TYPE_TOKEN)
|
||||
blockchain.withdrawAsset.retry(r.floID, r.asset, r.amount, r.id)
|
||||
})
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function confirmVaultWithdraw() {
|
||||
DB.query("SELECT id, floID, asset, asset_type, amount, txid FROM VaultTransactions WHERE mode=? AND r_status=?", [pCode.VAULT_MODE_WITHDRAW, pCode.STATUS_CONFIRMATION]).then(results => {
|
||||
results.forEach(r => {
|
||||
if (r.asset_type == pCode.ASSET_TYPE_COIN) {
|
||||
if (r.asset == "FLO")
|
||||
floBlockchainAPI.getTx(r.txid).then(tx => {
|
||||
if (!tx.blockheight || !tx.confirmations) //Still not confirmed
|
||||
return;
|
||||
DB.query("UPDATE VaultTransactions SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
||||
.then(result => console.info("FLO withdrawed:", r.floID, r.amount))
|
||||
.catch(error => console.error(error))
|
||||
}).catch(error => console.error(error));
|
||||
else if (r.asset == "BTC")
|
||||
btcOperator.getTx(r.txid).then(tx => {
|
||||
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
||||
return;
|
||||
DB.query("UPDATE VaultTransactions SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
||||
.then(result => console.info("BTC withdrawed:", r.floID, r.amount))
|
||||
.catch(error => console.error(error))
|
||||
}).catch(error => console.error(error));
|
||||
} else if (r.asset_type == pCode.ASSET_TYPE_TOKEN)
|
||||
floTokenAPI.getTx(r.txid).then(tx => {
|
||||
if (!tx.transactionDetails.blockheight || !tx.transactionDetails.confirmations) //Still not confirmed
|
||||
return;
|
||||
DB.query("UPDATE VaultTransactions SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
||||
.then(result => console.info("Token withdrawed:", r.floID, r.asset, r.amount))
|
||||
.catch(error => console.error(error));
|
||||
}).catch(error => console.error(error));
|
||||
})
|
||||
}).catch(error => console.error(error));
|
||||
}
|
||||
|
||||
verifyTx.BTC = function (sender, txid, group) {
|
||||
return new Promise((resolve, reject) => {
|
||||
btcOperator.getTx(txid).then(tx => {
|
||||
let vin_sender = tx.inputs.filter(v => floCrypto.isSameAddr(v.address, sender))
|
||||
if (!vin_sender.length)
|
||||
return reject([true, "Transaction not sent by the sender"]);
|
||||
if (vin_sender.length !== tx.inputs.length)
|
||||
return reject([true, "Transaction input containes other floIDs"]);
|
||||
if (!tx.blockhash)
|
||||
return reject([false, "Transaction not included in any block yet"]);
|
||||
if (!tx.confirmations)
|
||||
return reject([false, "Transaction not confirmed yet"]);
|
||||
let amount = tx.outputs.reduce((a, v) =>
|
||||
keys.sink_chest.includes(group, floCrypto.toFloID(v.address, { bech: [coinjs.bech32.version] })) ? a + parseFloat(v.value) : a, 0);
|
||||
if (amount == 0)
|
||||
return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes)
|
||||
else
|
||||
resolve(amount);
|
||||
}).catch(error => reject([false, error]))
|
||||
})
|
||||
}
|
||||
|
||||
function verifyConvert() {
|
||||
//Set all timeout convert request to refund mode (thus, asset will be refund if tx gets confirmed later)
|
||||
let req_timeout = new Date(Date.now() - REQUEST_TIMEOUT),
|
||||
to_refund_sql = "INSERT INTO RefundConvert (floID, in_txid, asset_type, asset, r_status)" +
|
||||
" SELECT floID, in_txid, ? AS asset_type, ? AS asset, r_status FROM DirectConvert" +
|
||||
" WHERE r_status=? AND locktime<? AND mode=?";
|
||||
let txQueries = [];
|
||||
txQueries.push([to_refund_sql, [pCode.ASSET_TYPE_TOKEN, floGlobals.currency, pCode.STATUS_PENDING, req_timeout, pCode.CONVERT_MODE_GET]]);
|
||||
txQueries.push([to_refund_sql, [pCode.ASSET_TYPE_COIN, "BTC", pCode.STATUS_PENDING, req_timeout, pCode.CONVERT_MODE_PUT]]);
|
||||
txQueries.push(["UPDATE DirectConvert SET r_status=? WHERE r_status=? AND locktime<?", [pCode.STATUS_REJECTED, pCode.STATUS_PENDING, req_timeout]]);
|
||||
DB.transaction(txQueries).then(result => {
|
||||
DB.query("SELECT id, floID, mode, in_txid, amount, quantity FROM DirectConvert WHERE r_status=? AND coin=?", [pCode.STATUS_PENDING, "BTC"]).then(results => {
|
||||
results.forEach(r => {
|
||||
if (r.mode == pCode.CONVERT_MODE_GET) {
|
||||
verifyTx.token(r.floID, r.in_txid, keys.sink_groups.CONVERT, true).then(({ amount }) => {
|
||||
if (r.amount !== amount)
|
||||
throw ([true, "Transaction amount mismatched in blockchain"]);
|
||||
conversion_rates.BTC_INR().then(rate => {
|
||||
blockchain.convertToCoin.init(r.floID, "BTC", amount, rate, r.id)
|
||||
}).catch(error => console.error(error))
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
if (error[0])
|
||||
DB.query("UPDATE DirectConvert SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id])
|
||||
.then(_ => null).catch(error => console.error(error));
|
||||
});
|
||||
} else if (r.mode == pCode.CONVERT_MODE_PUT) {
|
||||
verifyTx.BTC(r.floID, r.in_txid, keys.sink_groups.CONVERT).then(quantity => {
|
||||
if (r.quantity !== quantity)
|
||||
throw ([true, "Transaction quantity mismatched in blockchain"]);
|
||||
conversion_rates.BTC_INR().then(rate => {
|
||||
blockchain.convertFromCoin.init(r.floID, quantity, rate, r.id)
|
||||
}).catch(error => console.error(error))
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
if (error[0])
|
||||
DB.query("UPDATE DirectConvert SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id])
|
||||
.then(_ => null).catch(error => console.error(error));
|
||||
});
|
||||
}
|
||||
})
|
||||
}).catch(error => console.error(error))
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function retryConvert() {
|
||||
DB.query("SELECT id, floID, mode, amount, quantity FROM DirectConvert WHERE r_status=? AND coin=?", [pCode.STATUS_PROCESSING, "BTC"]).then(results => {
|
||||
results.forEach(r => {
|
||||
if (r.mode == pCode.CONVERT_MODE_GET)
|
||||
blockchain.convertToCoin.retry(r.floID, "BTC", r.quantity, r.id);
|
||||
else if (r.mode == pCode.CONVERT_MODE_PUT)
|
||||
blockchain.convertFromCoin.retry(r.floID, r.amount, r.id)
|
||||
})
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function confirmConvert() {
|
||||
DB.query("SELECT id, floID, mode, amount, quantity, out_txid FROM DirectConvert WHERE r_status=? AND coin=?", [pCode.STATUS_CONFIRMATION, "BTC"]).then(results => {
|
||||
results.forEach(r => {
|
||||
if (r.mode == pCode.CONVERT_MODE_GET)
|
||||
btcOperator.getTx(r.out_txid).then(tx => {
|
||||
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
||||
return;
|
||||
DB.query("UPDATE DirectConvert SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
||||
.then(result => console.info(`${r.floID} converted ${r.amount} to ${r.quantity} BTC`))
|
||||
.catch(error => console.error(error))
|
||||
}).catch(error => console.error(error));
|
||||
else if (r.mode == pCode.CONVERT_MODE_PUT)
|
||||
floTokenAPI.getTx(r.out_txid).then(tx => {
|
||||
if (!tx.transactionDetails.blockheight || !tx.transactionDetails.confirmations) //Still not confirmed
|
||||
return;
|
||||
DB.query("UPDATE DirectConvert SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
||||
.then(result => console.info(`${r.floID} converted ${r.quantity} BTC to ${r.amount}`))
|
||||
.catch(error => console.error(error));
|
||||
}).catch(error => console.error(error));
|
||||
})
|
||||
}).catch(error => console.error(error));
|
||||
}
|
||||
|
||||
function verifyConvertFundDeposit() {
|
||||
DB.query("SELECT id, mode, txid, coin FROM ConvertFund WHERE r_status=? AND coin=?", [pCode.STATUS_PROCESSING, "BTC"]).then(results => {
|
||||
results.forEach(r => {
|
||||
if (r.mode == pCode.CONVERT_MODE_GET) { //deposit currency
|
||||
verifyTx.token(floGlobals.adminID, r.txid, keys.sink_groups.CONVERT, true).then(({ amount }) => {
|
||||
DB.query("UPDATE ConvertFund SET r_status=?, amount=? WHERE id=?", [pCode.STATUS_SUCCESS, amount, r.id])
|
||||
.then(result => console.info(`Deposit-fund ${amount} ${floGlobals.currency} successful`))
|
||||
.catch(error => console.error(error));
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
if (error[0])
|
||||
DB.query("UPDATE ConvertFund SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id])
|
||||
.then(_ => null).catch(error => console.error(error));
|
||||
});
|
||||
} else if (r.mode == pCode.CONVERT_MODE_PUT) {//deposit coin
|
||||
verifyTx.BTC(floGlobals.adminID, r.txid, keys.sink_groups.CONVERT).then(quantity => {
|
||||
DB.query("UPDATE ConvertFund SET r_status=?, quantity=? WHERE id=?", [pCode.STATUS_SUCCESS, quantity, r.id])
|
||||
.then(result => console.info(`Deposit-fund ${quantity} ${r.coin} successful`))
|
||||
.catch(error => console.error(error));
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
if (error[0])
|
||||
DB.query("UPDATE ConvertFund SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id])
|
||||
.then(_ => null).catch(error => console.error(error));
|
||||
});
|
||||
}
|
||||
})
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function retryConvertFundWithdraw() {
|
||||
DB.query("SELECT id, mode, coin, quantity, amount FROM ConvertFund WHERE r_status=? AND coin=?", [pCode.STATUS_PENDING, "BTC"]).then(results => {
|
||||
results.forEach(r => {
|
||||
if (r.mode == pCode.CONVERT_MODE_GET) //withdraw coin
|
||||
blockchain.convertFundWithdraw.retry(r.coin, r.quantity, r.id);
|
||||
else if (r.mode == pCode.CONVERT_MODE_PUT) //withdraw currency
|
||||
blockchain.convertFundWithdraw.retry(floGlobals.currency, r.amount, r.id);
|
||||
})
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function confirmConvertFundWithdraw() {
|
||||
DB.query("SELECT * FROM ConvertFund WHERE r_status=? AND coin=?", [pCode.STATUS_CONFIRMATION, "BTC"]).then(results => {
|
||||
results.forEach(r => {
|
||||
if (r.mode == pCode.CONVERT_MODE_GET) { //withdraw coin
|
||||
btcOperator.getTx(r.txid).then(tx => {
|
||||
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
||||
return;
|
||||
DB.query("UPDATE ConvertFund SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
||||
.then(result => console.info(`Withdraw-fund ${r.quantity} ${r.coin} successful`))
|
||||
.catch(error => console.error(error))
|
||||
}).catch(error => console.error(error));
|
||||
} else if (r.mode == pCode.CONVERT_MODE_PUT) {//withdraw currency
|
||||
floTokenAPI.getTx(r.txid).then(tx => {
|
||||
if (!tx.transactionDetails.blockheight || !tx.transactionDetails.confirmations) //Still not confirmed
|
||||
return;
|
||||
DB.query("UPDATE ConvertFund SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
||||
.then(result => console.info(`Withdraw-fund ${r.amount} ${floGlobals.currency} successful`))
|
||||
.catch(error => console.error(error));
|
||||
}).catch(error => console.error(error));
|
||||
}
|
||||
})
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function verifyConvertRefund() {
|
||||
DB.query("SELECT id, floID, asset_type, asset, in_txid FROM RefundConvert WHERE r_status=?", [pCode.STATUS_PENDING]).then(results => {
|
||||
results.forEach(r => {
|
||||
if (r.ASSET_TYPE_COIN) {
|
||||
if (r.asset == "BTC") //Convert is only for BTC right now
|
||||
verifyTx.BTC(r.floID, r.in_txid, keys.sink_groups.CONVERT)
|
||||
.then(amount => blockchain.refundConvert.init(r.floID, r.asset, amount, r.id))
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
if (error[0])
|
||||
DB.query("UPDATE RefundConvert SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id])
|
||||
.then(_ => null).catch(error => console.error(error));
|
||||
});
|
||||
} else if (r.ASSET_TYPE_TOKEN)
|
||||
verifyTx.token(r.floID, r.in_txid, keys.sink_groups.CONVERT, true).then(({ amount }) => {
|
||||
blockchain.refundConvert.init(r.floID, floGlobals.currency, amount, r.id);
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
if (error[0])
|
||||
DB.query("UPDATE RefundConvert SET r_status=? WHERE id=?", [pCode.STATUS_REJECTED, r.id])
|
||||
.then(_ => null).catch(error => console.error(error));
|
||||
});
|
||||
})
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function retryConvertRefund() {
|
||||
DB.query("SELECT id, floID, asset, amount FROM RefundConvert WHERE r_status=?", [pCode.STATUS_PROCESSING]).then(results => {
|
||||
results.forEach(r => blockchain.refundConvert.retry(r.floID, r.asset, r.amount, r.id))
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function confirmConvertRefund() {
|
||||
DB.query("SELECT * FROM RefundConvert WHERE r_status=?", [pCode.STATUS_CONFIRMATION]).then(results => {
|
||||
results.forEach(r => {
|
||||
if (r.ASSET_TYPE_COIN) {
|
||||
if (r.asset == "BTC") //Convert is only for BTC right now
|
||||
btcOperator.getTx(r.out_txid).then(tx => {
|
||||
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
||||
return;
|
||||
DB.query("UPDATE RefundConvert SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
||||
.then(result => console.info(`Refunded ${r.amount} ${r.asset} to ${r.floID}`))
|
||||
.catch(error => console.error(error))
|
||||
}).catch(error => console.error(error));
|
||||
} else if (r.ASSET_TYPE_TOKEN)
|
||||
floTokenAPI.getTx(r.out_txid).then(tx => {
|
||||
if (!tx.transactionDetails.blockheight || !tx.transactionDetails.confirmations) //Still not confirmed
|
||||
return;
|
||||
DB.query("UPDATE RefundConvert SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
||||
.then(result => console.info(`Refunded ${r.amount} ${r.asset} to ${r.floID}`))
|
||||
.catch(error => console.error(error));
|
||||
}).catch(error => console.error(error));
|
||||
})
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function retryBondClosing() {
|
||||
DB.query("SELECT id, floID, amount, btc_net, usd_net FROM CloseBondTransact WHERE r_status=?", [pCode.STATUS_PENDING]).then(results => {
|
||||
results.forEach(r => blockchain.bondTransact.retry(r.floID, r.amount, r.btc_net, r.usd_net, r.id))
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function confirmBondClosing() {
|
||||
DB.query("SELECT * FROM CloseBondTransact WHERE r_status=?", [pCode.STATUS_CONFIRMATION]).then(results => {
|
||||
results.forEach(r => {
|
||||
btcOperator.getTx(r.txid).then(tx => {
|
||||
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
||||
return;
|
||||
let closeBondString = bond_util.stringify.end(r.bond_id, r.end_date, r.btc_net, r.usd_net, r.amount, r.ref_sign, r.txid);
|
||||
floBlockchainAPI.writeData(keys.node_id, closeBondString, keys.node_priv, bond_util.config.adminID).then(txid => {
|
||||
DB.query("UPDATE CloseBondTransact SET r_status=?, close_id=? WHERE id=?", [pCode.STATUS_SUCCESS, txid, r.id])
|
||||
.then(result => console.info("Bond closed:", r.bond_id))
|
||||
.catch(error => console.error(error));
|
||||
}).catch(error => console.error(error))
|
||||
}).catch(error => console.error(error));
|
||||
})
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function retryFundClosing() {
|
||||
DB.query("SELECT id, floID, amount, btc_net, usd_net FROM CloseFundTransact WHERE r_status=?", [pCode.STATUS_PENDING]).then(results => {
|
||||
results.forEach(r => blockchain.fundTransact.retry(r.floID, r.amount, r.btc_net, r.usd_net, r.id))
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function confirmFundClosing() {
|
||||
DB.query("SELECT * FROM CloseFundTransact WHERE r_status=?", [pCode.STATUS_CONFIRMATION]).then(results => {
|
||||
results.forEach(r => {
|
||||
btcOperator.getTx(r.txid).then(tx => {
|
||||
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
||||
return;
|
||||
let closeFundString = fund_util.stringify.end(r.fund_id, r.floID, r.end_date, r.btc_net, r.usd_net, r.amount, r.ref_sign, r.txid);
|
||||
floBlockchainAPI.writeData(keys.node_id, closeFundString, keys.node_priv, fund_util.config.adminID).then(txid => {
|
||||
DB.query("UPDATE CloseFundTransact SET r_status=?, close_id=? WHERE id=?", [pCode.STATUS_SUCCESS, txid, r.id])
|
||||
.then(result => console.info("Fund investment closed:", r.fund_id, r.floID))
|
||||
.catch(error => console.error(error));
|
||||
}).catch(error => console.error(error))
|
||||
}).catch(error => console.error(error));
|
||||
})
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
//Periodic Process
|
||||
|
||||
function processAll() {
|
||||
//deposit-withdraw asset balance
|
||||
if (keys.sink_chest.list(keys.sink_groups.EXCHANGE).length) {
|
||||
confirmDepositFLO();
|
||||
confirmDepositToken();
|
||||
retryVaultWithdrawal();
|
||||
confirmVaultWithdraw();
|
||||
}
|
||||
//convert service
|
||||
if (keys.sink_chest.list(keys.sink_groups.CONVERT).length) {
|
||||
verifyConvert();
|
||||
retryConvert();
|
||||
confirmConvert();
|
||||
verifyConvertFundDeposit();
|
||||
retryConvertFundWithdraw();
|
||||
confirmConvertFundWithdraw();
|
||||
verifyConvertRefund();
|
||||
retryConvertRefund();
|
||||
confirmConvertRefund();
|
||||
}
|
||||
//blockchain-bond service
|
||||
if (keys.sink_chest.list(keys.sink_groups.BLOCKCHAIN_BONDS).length) {
|
||||
retryBondClosing();
|
||||
confirmBondClosing();
|
||||
}
|
||||
//bob's fund service
|
||||
if (keys.sink_chest.list(keys.sink_groups.EXCHANGE).length) {
|
||||
retryFundClosing();
|
||||
confirmFundClosing();
|
||||
}
|
||||
}
|
||||
|
||||
var lastSyncBlockHeight = 0;
|
||||
|
||||
function periodicProcess() {
|
||||
floBlockchainAPI.promisedAPI('api/blocks?limit=1').then(result => {
|
||||
if (lastSyncBlockHeight < result.blocks[0].height) {
|
||||
lastSyncBlockHeight = result.blocks[0].height;
|
||||
processAll();
|
||||
console.log("Last Block :", lastSyncBlockHeight);
|
||||
}
|
||||
}).catch(error => console.error(error));
|
||||
}
|
||||
|
||||
function periodicProcess_start() {
|
||||
periodicProcess_stop();
|
||||
periodicProcess();
|
||||
assetList.forEach(asset => coupling.initiate(asset, true));
|
||||
price.storeHistory.start();
|
||||
periodicProcess.instance = setInterval(periodicProcess, PERIOD_INTERVAL);
|
||||
}
|
||||
|
||||
function periodicProcess_stop() {
|
||||
if (periodicProcess.instance !== undefined) {
|
||||
clearInterval(periodicProcess.instance);
|
||||
delete periodicProcess.instance;
|
||||
}
|
||||
coupling.stopAll();
|
||||
price.storeHistory.stop();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
blockchain,
|
||||
periodicProcess: {
|
||||
start: periodicProcess_start,
|
||||
stop: periodicProcess_stop
|
||||
},
|
||||
set assetList(assets) {
|
||||
assetList = assets;
|
||||
},
|
||||
set updateBalance(f) {
|
||||
updateBalance = f;
|
||||
}
|
||||
}
|
||||
@ -1,65 +1,76 @@
|
||||
'use strict';
|
||||
|
||||
const keys = require('../keys');
|
||||
const K_Bucket = require('../../docs/scripts/floExchangeAPI').K_Bucket;
|
||||
const slave = require('./slave');
|
||||
const sync = require('./sync');
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const {
|
||||
SINK_KEY_INDICATOR,
|
||||
SHARE_THRESHOLD
|
||||
} = require("../_constants")["backup"];
|
||||
const { BACKUP_INTERVAL } = require("../_constants")["backup"];
|
||||
const { DISCARD_COOLDOWN } = require("../_constants")["keys"];
|
||||
|
||||
var DB, app, wss, tokenList; //Container for database and app
|
||||
var _app, _wss, tokenList; //Container for app and wss
|
||||
var nodeList, nodeURL, nodeKBucket; //Container for (backup) node list
|
||||
var nodeShares = null,
|
||||
connectedSlaves = {},
|
||||
mod = null;
|
||||
const connectedSlaves = {},
|
||||
shares_collected = {},
|
||||
shares_pending = {},
|
||||
discarded_sinks = [];
|
||||
|
||||
var _mode = null;
|
||||
const SLAVE_MODE = 0,
|
||||
MASTER_MODE = 1;
|
||||
|
||||
//Shares
|
||||
function generateShares(sinkKey) {
|
||||
let nextNodes = nodeKBucket.nextNode(global.myFloID, null),
|
||||
let nextNodes = nodeKBucket.nextNode(keys.node_id, null),
|
||||
aliveNodes = Object.keys(connectedSlaves);
|
||||
if (nextNodes.length == 0) //This is the last node in nodeList
|
||||
return false;
|
||||
else if (aliveNodes.length == 0) //This is the last node in nodeList
|
||||
return null;
|
||||
else {
|
||||
let N = nextNodes.length + 1,
|
||||
th = Math.ceil(aliveNodes.length * SHARE_THRESHOLD) + 1,
|
||||
shares, refShare, mappedShares = {};
|
||||
shares = floCrypto.createShamirsSecretShares(sinkKey, N, th);
|
||||
refShare = shares.pop();
|
||||
for (let i in nextNodes)
|
||||
mappedShares[nextNodes[i]] = [refShare, shares[i]].join("|");
|
||||
return mappedShares;
|
||||
}
|
||||
nextNodes.unshift(keys.node_id);
|
||||
aliveNodes.unshift(keys.node_id);
|
||||
let shares, mappedShares = {};
|
||||
shares = keys.generateShares(sinkKey, nextNodes.length, aliveNodes.length);
|
||||
for (let i in nextNodes)
|
||||
mappedShares[nextNodes[i]] = shares[i];
|
||||
return mappedShares;
|
||||
}
|
||||
|
||||
function sendShare(ws, sinkID, keyShare) {
|
||||
ws.send(JSON.stringify({
|
||||
function sendShares(ws, sinkID) {
|
||||
if (!(sinkID in shares_pending) || !(ws.floID in shares_pending[sinkID].shares))
|
||||
return;
|
||||
let { ref, group } = shares_pending[sinkID],
|
||||
shares = shares_pending[sinkID].shares[ws.floID];
|
||||
delete shares_pending[sinkID].shares[ws.floID]; //delete the share after sending it to respective slave
|
||||
shares.forEach(s => ws.send(JSON.stringify({
|
||||
command: "SINK_SHARE",
|
||||
sinkID,
|
||||
keyShare: floCrypto.encryptData(keyShare, ws.pubKey)
|
||||
}));
|
||||
sinkID, ref, group,
|
||||
share: floCrypto.encryptData(s, ws.pubKey)
|
||||
})));
|
||||
}
|
||||
|
||||
function sendSharesToNodes(sinkID, shares) {
|
||||
nodeShares = shares;
|
||||
function sendSharesToNodes(sinkID, group, shares) {
|
||||
if (discarded_sinks.includes(sinkID)) { //sinkID is discarded, abort the new shares
|
||||
let i = discarded_sinks.findIndex(sinkID);
|
||||
discarded_sinks.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
let ref = Date.now();
|
||||
shares_pending[sinkID] = { shares, group, ref };
|
||||
if (keys.node_id in shares) {
|
||||
shares_pending[sinkID].shares[keys.node_id].forEach(s =>
|
||||
keys.addShare(group, sinkID, ref, s).then(_ => null).catch(error => console.error(error)));
|
||||
delete shares_pending[sinkID].shares[keys.node_id];
|
||||
}
|
||||
for (let node in shares)
|
||||
if (node in connectedSlaves)
|
||||
sendShare(connectedSlaves[node], sinkID, shares[node]);
|
||||
sendShares(connectedSlaves[node], sinkID);
|
||||
keys.sink_chest.set_id(group, sinkID, ref);
|
||||
}
|
||||
|
||||
function storeSink(sinkID, sinkPrivKey) {
|
||||
global.sinkID = sinkID;
|
||||
global.sinkPrivKey = sinkPrivKey;
|
||||
let encryptedKey = Crypto.AES.encrypt(SINK_KEY_INDICATOR + sinkPrivKey, global.myPrivKey);
|
||||
DB.query('INSERT INTO sinkShares (floID, share) VALUE (?, ?) ON DUPLICATE KEY UPDATE share=?', [sinkID, encryptedKey, encryptedKey])
|
||||
.then(_ => console.log('SinkID:', sinkID, '|SinkEnKey:', encryptedKey))
|
||||
.catch(error => console.error(error));
|
||||
function requestShare(ws, group, sinkID) {
|
||||
ws.send(JSON.stringify({
|
||||
command: "SEND_SHARE",
|
||||
group, sinkID,
|
||||
pubKey: keys.node_pub
|
||||
}));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -97,42 +108,55 @@ function transferMoneyToNewSink(oldSinkID, oldSinkKey, newSink) {
|
||||
}
|
||||
*/
|
||||
|
||||
const collectShares = {};
|
||||
collectShares.retrive = function(floID, sinkID, share) {
|
||||
const self = this;
|
||||
if (!self.sinkID) {
|
||||
self.sinkID = sinkID;
|
||||
self.shares = {};
|
||||
} else if (self.sinkID !== sinkID)
|
||||
return console.error("Something is wrong! Slaves are sending different sinkID");
|
||||
if (share.startsWith(SINK_KEY_INDICATOR)) {
|
||||
let sinkKey = share.substring(SINK_KEY_INDICATOR.length);
|
||||
console.debug("Received sink:", sinkID);
|
||||
self.verify(sinkKey);
|
||||
} else
|
||||
self.shares[floID] = share.split("|");
|
||||
function collectAndCall(group, sinkID, callback, timeout = null) {
|
||||
if (!(callback instanceof Function))
|
||||
throw Error("callback should be a function");
|
||||
if (!(sinkID in shares_collected)) { //if not already collecting shares for sinkID, then initiate collection
|
||||
shares_collected[sinkID] = { group, ref: 0, callbacks: [], shares: {} };
|
||||
for (let floID in connectedSlaves)
|
||||
requestShare(connectedSlaves[floID], group, sinkID);
|
||||
keys.getShares(group, sinkID)
|
||||
.then(({ ref, shares }) => shares.forEach(s => collectShares(sinkID, ref, s)))
|
||||
.catch(error => console.error(error))
|
||||
}
|
||||
shares_collected[sinkID].callbacks.push(callback);
|
||||
if (timeout)
|
||||
setTimeout(() => {
|
||||
if (sinkID in shares_collected) {
|
||||
let i = shares_collected[sinkID].callbacks.indexOf(callback);
|
||||
delete shares_collected[sinkID].callbacks[i]; //deleting will empty the index, but space will be there so that order of other indexes are not affected
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
collectAndCall.isAlive = (sinkID, callbackRef) => (sinkID in shares_collected && shares_collected[sinkID].callbacks.indexOf(callbackRef) != -1);
|
||||
|
||||
function collectShares(sinkID, ref, share) {
|
||||
if (_mode !== MASTER_MODE)
|
||||
return console.warn("Not serving as master");
|
||||
if (!(sinkID in shares_collected))
|
||||
return console.debug("Received shares for sink thats not been collected right now");
|
||||
if (shares_collected[sinkID].ref > ref)
|
||||
return console.debug("Received expired share");
|
||||
else if (shares_collected[sinkID].ref < ref) {
|
||||
shares_collected[sinkID].ref = ref;
|
||||
shares_collected[sinkID].shares = [];
|
||||
}
|
||||
if (shares_collected[sinkID].shares.includes(share))
|
||||
return console.debug("Received duplicate share");
|
||||
shares_collected[sinkID].shares.push(share);
|
||||
try {
|
||||
let sinkKey = floCrypto.retrieveShamirSecret([].concat(...Object.values(self.shares)));
|
||||
console.debug("Retrived sink:", sinkID);
|
||||
self.verify(sinkKey);
|
||||
let sinkKey = floCrypto.retrieveShamirSecret(shares_collected[sinkID].shares);
|
||||
if (floCrypto.verifyPrivKey(sinkKey, sinkID)) {
|
||||
console.debug("Shares collected successfully for", sinkID);
|
||||
shares_collected[sinkID].callbacks.forEach(fn => fn instanceof Function ? fn(sinkKey) : null);
|
||||
delete shares_collected[sinkID];
|
||||
}
|
||||
} catch {
|
||||
//Unable to retrive sink private key. Waiting for more shares! Do nothing for now
|
||||
};
|
||||
}
|
||||
|
||||
collectShares.verify = function(sinkKey) {
|
||||
const self = this;
|
||||
if (floCrypto.verifyPrivKey(sinkKey, self.sinkID)) {
|
||||
let sinkID = self.sinkID;
|
||||
console.log("Shares collected successfully for", sinkID);
|
||||
self.active = false;
|
||||
delete self.sinkID;
|
||||
delete self.shares;
|
||||
storeSink(sinkID, sinkKey);
|
||||
sendSharesToNodes(sinkID, generateShares(sinkKey));
|
||||
}
|
||||
}
|
||||
|
||||
function connectWS(floID) {
|
||||
let url = nodeURL[floID];
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -144,11 +168,11 @@ function connectWS(floID) {
|
||||
|
||||
function connectToMaster(i = 0, init = false) {
|
||||
if (i >= nodeList.length) {
|
||||
console.error("No master is found, and myFloID is not in list. This should not happen!");
|
||||
console.error("No master is found and Node not in list. This should not happen!");
|
||||
process.exit(1);
|
||||
}
|
||||
let floID = nodeList[i];
|
||||
if (floID === myFloID)
|
||||
if (floID === keys.node_id)
|
||||
serveAsMaster(init);
|
||||
else
|
||||
connectWS(floID).then(ws => {
|
||||
@ -161,32 +185,16 @@ function connectToMaster(i = 0, init = false) {
|
||||
});
|
||||
}
|
||||
|
||||
//Node becomes master
|
||||
function serveAsMaster(init) {
|
||||
console.debug('Starting master process');
|
||||
slave.stop();
|
||||
mod = MASTER_MODE;
|
||||
informLiveNodes(init);
|
||||
app.resume();
|
||||
}
|
||||
|
||||
function serveAsSlave(ws, init) {
|
||||
console.debug('Starting slave process');
|
||||
app.pause();
|
||||
slave.start(ws, init);
|
||||
mod = SLAVE_MODE;
|
||||
}
|
||||
|
||||
function informLiveNodes(init) {
|
||||
let message = {
|
||||
floID: global.myFloID,
|
||||
floID: keys.node_id,
|
||||
type: "UPDATE_MASTER",
|
||||
pubKey: global.myPubKey,
|
||||
pubKey: keys.node_pub,
|
||||
req_time: Date.now()
|
||||
};
|
||||
message.sign = floCrypto.signData(message.type + "|" + message.req_time, global.myPrivKey);
|
||||
message.sign = floCrypto.signData(message.type + "|" + message.req_time, keys.node_priv);
|
||||
message = JSON.stringify(message);
|
||||
let nodes = nodeList.filter(n => n !== global.myFloID);
|
||||
let nodes = nodeList.filter(n => n !== keys.node_id);
|
||||
Promise.allSettled(nodes.map(n => connectWS(n))).then(result => {
|
||||
let flag = false;
|
||||
for (let i in result)
|
||||
@ -199,45 +207,29 @@ function informLiveNodes(init) {
|
||||
console.warn(`Node(${nodes[i]}) is offline`);
|
||||
if (init && flag)
|
||||
syncRequest();
|
||||
//Check if sinkKey or share available in DB
|
||||
DB.query("SELECT floID, share FROM sinkShares ORDER BY time_ DESC LIMIT 1").then(result => {
|
||||
if (result.length) {
|
||||
let share = Crypto.AES.decrypt(result[0].share, global.myPrivKey);
|
||||
if (share.startsWith(SINK_KEY_INDICATOR)) {
|
||||
//sinkKey is already present in DB, use it directly
|
||||
collectShares.active = false;
|
||||
global.sinkPrivKey = share.substring(SINK_KEY_INDICATOR.length);
|
||||
global.sinkID = floCrypto.getFloID(global.sinkPrivKey);
|
||||
if (global.sinkID != result[0].floID) {
|
||||
console.warn("sinkID and sinkKey in DB are not pair!");
|
||||
storeSink(global.sinkID, global.sinkPrivKey);
|
||||
}
|
||||
console.debug("Loaded sink:", global.sinkID);
|
||||
sendSharesToNodes(global.sinkID, generateShares(global.sinkPrivKey))
|
||||
} else {
|
||||
//Share is present in DB, try to collect remaining shares and retrive sinkKey
|
||||
collectShares.active = true;
|
||||
collectShares.retrive(global.myFloID, result[0].floID, share);
|
||||
}
|
||||
} else if (init) {
|
||||
if (flag) //Other nodes online, try to collect shares and retrive sinkKey
|
||||
collectShares.active = true;
|
||||
else {
|
||||
//No other node is active (possible 1st node to start exchange)
|
||||
console.log("Starting the exchange...");
|
||||
collectShares.active = false;
|
||||
let newSink = floCrypto.generateNewID();
|
||||
console.debug("Generated sink:", newSink.floID, newSink.privKey);
|
||||
storeSink(newSink.floID, newSink.privKey);
|
||||
sendSharesToNodes(newSink.floID, generateShares(newSink.privKey));
|
||||
}
|
||||
} else //This should not happen!
|
||||
console.error("Something is wrong! Node is not starting and no key/share present in DB");
|
||||
keys.getStoredList().then(stored_list => {
|
||||
if (Object.keys(stored_list).length) {
|
||||
keys.getDiscardedList().then(discarded_list => {
|
||||
let cur_time = Date.now();
|
||||
for (let group in stored_list)
|
||||
stored_list[group].forEach(id => {
|
||||
if (!(id in discarded_list))
|
||||
reconstructShares(group, id)
|
||||
else if (cur_time - discarded_list[id] < DISCARD_COOLDOWN) //sinkID still in cooldown period
|
||||
keys.sink_chest.set_id(group, id, null);
|
||||
});
|
||||
}).catch(error => console.error(error))
|
||||
} else if (init && !flag) {
|
||||
console.log("Starting the exchange...");
|
||||
//generate a sinkID for each group in starting list
|
||||
keys.sink_groups.initial_list.forEach(group =>
|
||||
generateSink(group).then(_ => null).catch(e => console.error(e)));
|
||||
}
|
||||
}).catch(error => console.error(error));
|
||||
});
|
||||
}
|
||||
|
||||
function syncRequest(cur = global.myFloID) {
|
||||
function syncRequest(cur = keys.node_id) {
|
||||
//Sync data from next available node
|
||||
let nextNode = nodeKBucket.nextNode(cur);
|
||||
if (!nextNode)
|
||||
@ -248,33 +240,161 @@ function syncRequest(cur = global.myFloID) {
|
||||
}
|
||||
|
||||
function updateMaster(floID) {
|
||||
let currentMaster = mod === MASTER_MODE ? global.myFloID : slave.masterWS.floID;
|
||||
let currentMaster = _mode === MASTER_MODE ? keys.node_id : slave.masterWS.floID;
|
||||
if (nodeList.indexOf(floID) < nodeList.indexOf(currentMaster))
|
||||
connectToMaster();
|
||||
}
|
||||
|
||||
function slaveConnect(floID, pubKey, ws) {
|
||||
function reconstructShares(group, sinkID) {
|
||||
if (_mode !== MASTER_MODE)
|
||||
return console.warn("Not serving as master");
|
||||
keys.sink_chest.set_id(group, sinkID, null);
|
||||
collectAndCall(group, sinkID, sinkKey => sendSharesToNodes(sinkID, group, generateShares(sinkKey)));
|
||||
}
|
||||
|
||||
function slaveConnect(floID, pubKey, ws, slave_sinks) {
|
||||
if (_mode !== MASTER_MODE)
|
||||
return console.warn("Not serving as master");
|
||||
ws.floID = floID;
|
||||
ws.pubKey = pubKey;
|
||||
connectedSlaves[floID] = ws;
|
||||
if (collectShares.active)
|
||||
ws.send(JSON.stringify({
|
||||
command: "SEND_SHARE",
|
||||
pubKey: global.myPubKey
|
||||
}));
|
||||
else if (nodeShares === null || //The 1st backup is connected
|
||||
Object.keys(connectedSlaves).length < Math.pow(SHARE_THRESHOLD, 2) * Object.keys(nodeShares).length) //re-calib shares for better
|
||||
sendSharesToNodes(global.sinkID, generateShares(global.sinkPrivKey))
|
||||
else if (nodeShares[floID])
|
||||
sendShare(ws, global.sinkID, nodeShares[floID]);
|
||||
|
||||
//Send shares if need to be delivered
|
||||
for (let sinkID in shares_pending)
|
||||
if (floID in shares_pending[sinkID].shares)
|
||||
sendShares(ws, sinkID);
|
||||
//Request shares if any
|
||||
for (let sinkID in shares_collected)
|
||||
requestShare(ws, shares_collected[sinkID].group, sinkID);
|
||||
//check if sinks in slaves are present
|
||||
if (slave_sinks instanceof Object) {
|
||||
for (let group in slave_sinks)
|
||||
for (let sinkID of slave_sinks[group]) {
|
||||
if (!keys.sink_chest.includes(group, sinkID))
|
||||
keys.checkIfDiscarded(sinkID)
|
||||
.then(result => result === false ? reconstructShares(group, sinkID) : null)
|
||||
.catch(error => console.error(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const eCode = require('../../docs/scripts/floExchangeAPI').errorCode;
|
||||
|
||||
function generateSink(group) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!keys.sink_groups.generate_list.includes(group))
|
||||
return reject(INVALID(eCode.INVALID_VALUE, `Invalid Group ${group}`));
|
||||
try {
|
||||
let newSink = floCrypto.generateNewID();
|
||||
console.debug("Generated sink:", group, newSink.floID);
|
||||
sendSharesToNodes(newSink.floID, group, generateShares(newSink.privKey));
|
||||
resolve(`Generated ${newSink.floID} (${group})`);
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function reshareSink(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floCrypto.validateAddr(id))
|
||||
return reject(INVALID(eCode.INVALID_VALUE, `Invalid ID ${id}`));
|
||||
else {
|
||||
let group = keys.sink_chest.find_group(id);
|
||||
if (!group)
|
||||
return reject(INVALID(eCode.NOT_FOUND, `ID ${id} not found`));
|
||||
else keys.checkIfDiscarded(id).then(result => {
|
||||
if (result)
|
||||
return reject(INVALID(eCode.NOT_FOUND, `ID is discarded`));
|
||||
try {
|
||||
reconstructShares(group, id);
|
||||
resolve(`Resharing ${id} (${group})`);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
}).catch(error => reject(error))
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function discardSink(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floCrypto.validateAddr(id))
|
||||
return reject(INVALID(eCode.INVALID_VALUE, `Invalid ID ${id}`));
|
||||
else if (!keys.sink_chest.find_group(id))
|
||||
return reject(INVALID(eCode.NOT_FOUND, `ID ${id} not found`));
|
||||
else keys.checkIfDiscarded(id).then(result => {
|
||||
if (result)
|
||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, `ID already discarded`));
|
||||
keys.discardSink(id).then(result => {
|
||||
console.debug("Discarded sink:", id);
|
||||
resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function checkForDiscardedSinks() {
|
||||
let cur_time = Date.now(),
|
||||
all_sinks = keys.sink_chest.get_all();
|
||||
for (let group in all_sinks)
|
||||
all_sinks[group].forEach(id => keys.checkIfDiscarded(id).then(result => {
|
||||
console.debug(group, id); //Check if group is correctly mapped, or if its changed by loop
|
||||
if (result != false) {
|
||||
if (cur_time - result > DISCARD_COOLDOWN)
|
||||
keys.sink_chest.rm_id(group, id);
|
||||
else
|
||||
keys.sink_chest.set_id(group, id, null);
|
||||
if (id in shares_collected && !discarded_sinks.includes(id))
|
||||
discarded_sinks.push(id);
|
||||
}
|
||||
}).catch(error => console.debug(error)))
|
||||
}
|
||||
|
||||
//Master interval process
|
||||
function intervalProcess() {
|
||||
checkForDiscardedSinks();
|
||||
}
|
||||
|
||||
intervalProcess.start = () => {
|
||||
intervalProcess.stop();
|
||||
intervalProcess.instance = setInterval(intervalProcess, BACKUP_INTERVAL);
|
||||
}
|
||||
|
||||
intervalProcess.stop = () => {
|
||||
if (intervalProcess.instance !== undefined) {
|
||||
clearInterval(intervalProcess.instance);
|
||||
delete intervalProcess.instance;
|
||||
}
|
||||
}
|
||||
|
||||
//Node becomes master
|
||||
function serveAsMaster(init) {
|
||||
console.info('Starting master process');
|
||||
slave.stop();
|
||||
_mode = MASTER_MODE;
|
||||
keys.sink_chest.reset();
|
||||
intervalProcess.start();
|
||||
informLiveNodes(init);
|
||||
_app.resume();
|
||||
}
|
||||
|
||||
//Node becomes slave
|
||||
function serveAsSlave(ws, init) {
|
||||
console.info('Starting slave process');
|
||||
intervalProcess.stop();
|
||||
_app.pause();
|
||||
slave.start(ws, init);
|
||||
_mode = SLAVE_MODE;
|
||||
}
|
||||
|
||||
//Transmistter
|
||||
function startBackupTransmitter(server) {
|
||||
wss = new WebSocket.Server({
|
||||
_wss = new WebSocket.Server({
|
||||
server
|
||||
});
|
||||
wss.on('connection', ws => {
|
||||
_wss.on('connection', ws => {
|
||||
ws.on('message', message => {
|
||||
//verify if from a backup node
|
||||
try {
|
||||
@ -302,10 +422,10 @@ function startBackupTransmitter(server) {
|
||||
updateMaster(request.floID);
|
||||
break;
|
||||
case "SLAVE_CONNECT":
|
||||
slaveConnect(request.floID, request.pubKey, ws);
|
||||
slaveConnect(request.floID, request.pubKey, ws, request.sinks);
|
||||
break;
|
||||
case "SINK_SHARE":
|
||||
collectShares.retrive(request.floID, request.sinkID, floCrypto.decryptData(request.share, global.myPrivKey))
|
||||
collectShares(request.sinkID, request.ref, floCrypto.decryptData(request.share, keys.node_priv))
|
||||
default:
|
||||
invalid = "Invalid Request Type";
|
||||
}
|
||||
@ -331,28 +451,32 @@ function startBackupTransmitter(server) {
|
||||
});
|
||||
}
|
||||
|
||||
function initProcess(a) {
|
||||
app = a;
|
||||
startBackupTransmitter(app.server);
|
||||
function initProcess(app) {
|
||||
_app = app;
|
||||
startBackupTransmitter(_app.server);
|
||||
connectToMaster(0, true);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: initProcess,
|
||||
collectAndCall,
|
||||
sink: {
|
||||
generate: generateSink,
|
||||
reshare: reshareSink,
|
||||
discard: discardSink
|
||||
},
|
||||
set nodeList(list) {
|
||||
nodeURL = list;
|
||||
nodeKBucket = new K_Bucket(floGlobals.adminID, Object.keys(nodeURL));
|
||||
nodeList = nodeKBucket.order;
|
||||
},
|
||||
get nodeList() {
|
||||
return nodeList;
|
||||
},
|
||||
set assetList(assets) {
|
||||
tokenList = assets.filter(a => a.toUpperCase() !== "FLO");
|
||||
},
|
||||
set DB(db) {
|
||||
DB = db;
|
||||
sync.DB = db;
|
||||
slave.DB = db;
|
||||
},
|
||||
get wss() {
|
||||
return wss;
|
||||
return _wss;
|
||||
}
|
||||
};
|
||||
@ -1,14 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
const keys = require("../keys");
|
||||
const DB = require("../database");
|
||||
|
||||
const {
|
||||
BACKUP_INTERVAL,
|
||||
BACKUP_SYNC_TIMEOUT,
|
||||
CHECKSUM_INTERVAL,
|
||||
SINK_KEY_INDICATOR,
|
||||
HASH_N_ROW
|
||||
} = require("../_constants")["backup"];
|
||||
|
||||
var DB; //Container for Database connection
|
||||
var masterWS = null; //Container for Master websocket connection
|
||||
|
||||
var intervalID = null;
|
||||
@ -20,19 +21,32 @@ function startSlaveProcess(ws, init) {
|
||||
//set masterWS
|
||||
ws.on('message', processDataFromMaster);
|
||||
masterWS = ws;
|
||||
//inform master
|
||||
let message = {
|
||||
floID: global.myFloID,
|
||||
pubKey: global.myPubKey,
|
||||
req_time: Date.now(),
|
||||
type: "SLAVE_CONNECT"
|
||||
}
|
||||
message.sign = floCrypto.signData(message.type + "|" + message.req_time, global.myPrivKey);
|
||||
ws.send(JSON.stringify(message));
|
||||
//start sync
|
||||
if (init)
|
||||
requestInstance.open();
|
||||
intervalID = setInterval(() => requestInstance.open(), BACKUP_INTERVAL);
|
||||
let sinks_stored = {};
|
||||
Promise.all([keys.getStoredList(), keys.getDiscardedList()]).then(result => {
|
||||
let stored_list = result[0],
|
||||
discarded_list = result[1];
|
||||
for (let group in stored_list) {
|
||||
sinks_stored[group] = [];
|
||||
for (let id of stored_list[group])
|
||||
if (!(id in discarded_list))
|
||||
sinks_stored[group].push(id);
|
||||
}
|
||||
}).catch(error => console.error(error)).finally(_ => {
|
||||
//inform master
|
||||
let message = {
|
||||
floID: keys.node_id,
|
||||
pubKey: keys.node_pub,
|
||||
sinks: sinks_stored,
|
||||
req_time: Date.now(),
|
||||
type: "SLAVE_CONNECT"
|
||||
}
|
||||
message.sign = floCrypto.signData(message.type + "|" + message.req_time, keys.node_priv);
|
||||
ws.send(JSON.stringify(message));
|
||||
//start sync
|
||||
if (init)
|
||||
requestInstance.open();
|
||||
intervalID = setInterval(() => requestInstance.open(), BACKUP_INTERVAL);
|
||||
})
|
||||
}
|
||||
|
||||
function stopSlaveProcess() {
|
||||
@ -50,16 +64,16 @@ function stopSlaveProcess() {
|
||||
|
||||
function requestBackupSync(checksum_trigger, ws) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query('SELECT MAX(timestamp) as last_time FROM _backup').then(result => {
|
||||
DB.query('SELECT MAX(u_time) as last_time FROM _backup').then(result => {
|
||||
let request = {
|
||||
floID: global.myFloID,
|
||||
pubKey: global.myPubKey,
|
||||
floID: keys.node_id,
|
||||
pubKey: keys.node_pub,
|
||||
type: "BACKUP_SYNC",
|
||||
last_time: result[0].last_time,
|
||||
checksum: checksum_trigger,
|
||||
req_time: Date.now()
|
||||
};
|
||||
request.sign = floCrypto.signData(request.type + "|" + request.req_time, global.myPrivKey);
|
||||
request.sign = floCrypto.signData(request.type + "|" + request.req_time, keys.node_priv);
|
||||
ws.send(JSON.stringify(request));
|
||||
resolve(request);
|
||||
}).catch(error => reject(error))
|
||||
@ -78,7 +92,7 @@ const requestInstance = {
|
||||
checksum_count_down: 0
|
||||
};
|
||||
|
||||
requestInstance.open = function(ws = null) {
|
||||
requestInstance.open = function (ws = null) {
|
||||
const self = this;
|
||||
//Check if there is an active request
|
||||
if (self.request) {
|
||||
@ -104,7 +118,7 @@ requestInstance.open = function(ws = null) {
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
requestInstance.close = function() {
|
||||
requestInstance.close = function () {
|
||||
const self = this;
|
||||
if (self.onetime)
|
||||
self.ws.close();
|
||||
@ -128,10 +142,10 @@ function processDataFromMaster(message) {
|
||||
processBackupData(message);
|
||||
else switch (message.command) {
|
||||
case "SINK_SHARE":
|
||||
storeSinkShare(message.sinkID, message.keyShare);
|
||||
storeSinkShare(message.group, message.sinkID, message.share, message.ref);
|
||||
break;
|
||||
case "SEND_SHARE":
|
||||
sendSinkShare(message.pubKey);
|
||||
sendSinkShare(message.group, message.sinkID, message.pubKey);
|
||||
break;
|
||||
case "REQUEST_ERROR":
|
||||
console.log(message.error);
|
||||
@ -144,30 +158,26 @@ function processDataFromMaster(message) {
|
||||
}
|
||||
}
|
||||
|
||||
function storeSinkShare(sinkID, keyShare) {
|
||||
let encryptedShare = Crypto.AES.encrypt(floCrypto.decryptData(keyShare, global.myPrivKey), global.myPrivKey);
|
||||
console.log(Date.now(), '|sinkID:', sinkID, '|EnShare:', encryptedShare);
|
||||
DB.query("INSERT INTO sinkShares (floID, share) VALUE (?, ?) ON DUPLICATE KEY UPDATE share=?", [sinkID, encryptedShare, encryptedShare])
|
||||
function storeSinkShare(group, sinkID, share, ref) {
|
||||
share = floCrypto.decryptData(share, keys.node_priv);
|
||||
keys.addShare(group, sinkID, ref, share)
|
||||
.then(_ => null).catch(error => console.error(error));
|
||||
}
|
||||
|
||||
function sendSinkShare(pubKey) {
|
||||
DB.query("SELECT floID, share FROM sinkShares ORDER BY time_ DESC LIMIT 1").then(result => {
|
||||
if (!result.length)
|
||||
return console.warn("No key-shares in DB!");
|
||||
let share = Crypto.AES.decrypt(result[0].share, global.myPrivKey);
|
||||
if (share.startsWith(SINK_KEY_INDICATOR))
|
||||
console.warn("Key is stored instead of share!");
|
||||
let response = {
|
||||
type: "SINK_SHARE",
|
||||
sinkID: result[0].floID,
|
||||
share: floCrypto.encryptData(share, pubKey),
|
||||
floID: global.myFloID,
|
||||
pubKey: global.myPubKey,
|
||||
req_time: Date.now()
|
||||
}
|
||||
response.sign = floCrypto.signData(response.type + "|" + response.req_time, global.myPrivKey); //TODO: strengthen signature
|
||||
masterWS.send(JSON.stringify(response));
|
||||
function sendSinkShare(group, sinkID, pubKey) {
|
||||
keys.getShares(group, sinkID).then(({ ref, shares }) => {
|
||||
shares.forEach(s => {
|
||||
let response = {
|
||||
type: "SINK_SHARE",
|
||||
sinkID, ref,
|
||||
share: floCrypto.encryptData(s, pubKey),
|
||||
floID: keys.node_id,
|
||||
pubKey: keys.node_pub,
|
||||
req_time: Date.now()
|
||||
}
|
||||
response.sign = floCrypto.signData(response.type + "|" + response.req_time, keys.node_priv); //TODO: strengthen signature
|
||||
masterWS.send(JSON.stringify(response));
|
||||
})
|
||||
}).catch(error => console.error(error));
|
||||
}
|
||||
|
||||
@ -186,7 +196,7 @@ function processBackupData(response) {
|
||||
console.log("Backup Sync completed successfully");
|
||||
self.close();
|
||||
} else
|
||||
console.log("Waiting for come re-sync data");
|
||||
console.log("Waiting for re-sync data");
|
||||
}).catch(_ => {
|
||||
console.warn("Backup Sync was not successful");
|
||||
self.close();
|
||||
@ -247,8 +257,8 @@ function storeBackupData(cache_promises, checksum_ref) {
|
||||
resolve(true);
|
||||
else
|
||||
verifyChecksum(checksum_ref)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
});
|
||||
})
|
||||
})
|
||||
@ -271,7 +281,7 @@ function storeBackupData(cache_promises, checksum_ref) {
|
||||
|
||||
}
|
||||
|
||||
storeBackupData.commit = function(data, result) {
|
||||
storeBackupData.commit = function (data, result) {
|
||||
let promises = [];
|
||||
for (let i = 0; i < data.length; i++)
|
||||
switch (result[i].status) {
|
||||
@ -280,7 +290,7 @@ storeBackupData.commit = function(data, result) {
|
||||
break;
|
||||
case "rejected":
|
||||
console.error(result[i].reason);
|
||||
promises.push(DB.query("UPDATE _backupCache SET status=FALSE WHERE id=?", data[i].id));
|
||||
promises.push(DB.query("UPDATE _backupCache SET fail=TRUE WHERE id=?", data[i].id));
|
||||
break;
|
||||
}
|
||||
return Promise.allSettled(promises);
|
||||
@ -289,13 +299,13 @@ storeBackupData.commit = function(data, result) {
|
||||
function updateBackupTable(add_data, delete_data) {
|
||||
//update _backup table for added data
|
||||
DB.transaction(add_data.map(r => [
|
||||
"INSERT INTO _backup (t_name, id, mode, timestamp) VALUE (?, ?, TRUE, ?) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=?",
|
||||
[r.t_name, r.id, validateValue(r.timestamp), validateValue(r.timestamp)]
|
||||
"INSERT INTO _backup (t_name, id, mode, u_time) VALUE (?, ?, TRUE, ?) ON DUPLICATE KEY UPDATE mode=TRUE, u_time=?",
|
||||
[r.t_name, r.id, validateValue(r.u_time), validateValue(r.u_time)]
|
||||
])).then(_ => null).catch(error => console.error(error));
|
||||
//update _backup table for deleted data
|
||||
DB.transaction(delete_data.map(r => [
|
||||
"INSERT INTO _backup (t_name, id, mode, timestamp) VALUE (?, ?, NULL, ?) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=?",
|
||||
[r.t_name, r.id, validateValue(r.timestamp), validateValue(r.timestamp)]
|
||||
"INSERT INTO _backup (t_name, id, mode, u_time) VALUE (?, ?, NULL, ?) ON DUPLICATE KEY UPDATE mode=NULL, u_time=?",
|
||||
[r.t_name, r.id, validateValue(r.u_time), validateValue(r.u_time)]
|
||||
])).then(_ => null).catch(error => console.error(error));
|
||||
}
|
||||
|
||||
@ -314,16 +324,15 @@ function updateTableData(table, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!data.length)
|
||||
return resolve(null);
|
||||
let cols = Object.keys(data[0]),
|
||||
_mark = "(" + Array(cols.length).fill('?') + ")";
|
||||
let values = data.map(r => cols.map(c => validateValue(r[c]))).flat();
|
||||
let statement = `INSERT INTO ${table} (${cols}) VALUES ${Array(data.length).fill(_mark)}` +
|
||||
let cols = Object.keys(data[0]);
|
||||
let values = data.map(r => cols.map(c => validateValue(r[c])));
|
||||
let statement = `INSERT INTO ${table} (${cols}) VALUES ?` +
|
||||
" ON DUPLICATE KEY UPDATE " + cols.map(c => `${c}=VALUES(${c})`).join();
|
||||
DB.query(statement, values).then(_ => resolve(true)).catch(error => reject(error));
|
||||
DB.query(statement, [values]).then(_ => resolve(true)).catch(error => reject(error));
|
||||
})
|
||||
}
|
||||
|
||||
const validateValue = val => (typeof val === "string" && /\.\d{3}Z$/.test(val)) ? val.substring(0, val.length - 1) : val;
|
||||
const validateValue = val => (typeof val === "string" && /\.\d{3}Z$/.test(val)) ? new Date(val) : val;
|
||||
|
||||
function verifyChecksum(checksum_ref) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -351,13 +360,13 @@ function requestHash(tables) {
|
||||
//TODO: resync only necessary data (instead of entire table)
|
||||
let self = requestInstance;
|
||||
let request = {
|
||||
floID: global.myFloID,
|
||||
pubKey: global.myPubKey,
|
||||
floID: keys.node_id,
|
||||
pubKey: keys.node_pub,
|
||||
type: "HASH_SYNC",
|
||||
tables: tables,
|
||||
req_time: Date.now()
|
||||
};
|
||||
request.sign = floCrypto.signData(request.type + "|" + request.req_time, global.myPrivKey);
|
||||
request.sign = floCrypto.signData(request.type + "|" + request.req_time, keys.node_priv);
|
||||
self.ws.send(JSON.stringify(request));
|
||||
self.request = request;
|
||||
self.checksum = null;
|
||||
@ -394,7 +403,7 @@ function verifyHash(hashes) {
|
||||
//Data to be deleted (incorrect data will be added by resync)
|
||||
let id_end = result[t].value[1].map(i => i * HASH_N_ROW); //eg if i=2 AND H_R_C = 5 then id_end = 2 * 5 = 10 (ie, range 6-10)
|
||||
Promise.allSettled(id_end.map(i =>
|
||||
DB.query(`DELETE FROM ${tables[t]} WHERE id BETWEEN ${i - HASH_N_ROW + 1} AND ${i}`))) //eg, i - HASH_N_ROW + 1 = 10 - 5 + 1 = 6
|
||||
DB.query(`DELETE FROM ${tables[t]} WHERE id BETWEEN ${i - HASH_N_ROW + 1} AND ${i}`))) //eg, i - HASH_N_ROW + 1 = 10 - 5 + 1 = 6
|
||||
.then(_ => null);
|
||||
} else
|
||||
console.error(result[t].reason);
|
||||
@ -406,20 +415,17 @@ function verifyHash(hashes) {
|
||||
|
||||
function requestTableChunks(tables, ws) {
|
||||
let request = {
|
||||
floID: global.myFloID,
|
||||
pubKey: global.myPubKey,
|
||||
floID: keys.node_id,
|
||||
pubKey: keys.node_pub,
|
||||
type: "RE_SYNC",
|
||||
tables: tables,
|
||||
req_time: Date.now()
|
||||
};
|
||||
request.sign = floCrypto.signData(request.type + "|" + request.req_time, global.myPrivKey);
|
||||
request.sign = floCrypto.signData(request.type + "|" + request.req_time, keys.node_priv);
|
||||
ws.send(JSON.stringify(request));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
set DB(db) {
|
||||
DB = db;
|
||||
},
|
||||
get masterWS() {
|
||||
return masterWS;
|
||||
},
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
const DB = require("../database");
|
||||
|
||||
const {
|
||||
HASH_N_ROW
|
||||
} = require("../_constants")["backup"];
|
||||
|
||||
var DB; //Container for database
|
||||
|
||||
//Backup Transfer
|
||||
function sendBackupData(timestamp, checksum, ws) {
|
||||
if (!timestamp) timestamp = 0;
|
||||
else if (typeof timestamp === "string" && /\.\d{3}Z$/.test(timestamp))
|
||||
timestamp = timestamp.substring(0, timestamp.length - 1);
|
||||
function sendBackupData(last_time, checksum, ws) {
|
||||
if (!last_time) last_time = 0;
|
||||
else if (typeof last_time === "string" && /\.\d{3}Z$/.test(last_time))
|
||||
last_time = last_time.substring(0, last_time.length - 1);
|
||||
let promises = [
|
||||
backupSync_data(timestamp, ws),
|
||||
backupSync_delete(timestamp, ws)
|
||||
backupSync_data(last_time, ws),
|
||||
backupSync_delete(last_time, ws)
|
||||
];
|
||||
if (checksum)
|
||||
promises.push(backupSync_checksum(ws));
|
||||
@ -37,9 +37,9 @@ function sendBackupData(timestamp, checksum, ws) {
|
||||
});
|
||||
}
|
||||
|
||||
function backupSync_delete(timestamp, ws) {
|
||||
function backupSync_delete(last_time, ws) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT * FROM _backup WHERE mode is NULL AND timestamp > ?", [timestamp]).then(result => {
|
||||
DB.query("SELECT * FROM _backup WHERE mode is NULL AND u_time > ?", [last_time]).then(result => {
|
||||
ws.send(JSON.stringify({
|
||||
command: "SYNC_DELETE",
|
||||
delete_data: result
|
||||
@ -52,7 +52,7 @@ function backupSync_delete(timestamp, ws) {
|
||||
})
|
||||
}
|
||||
|
||||
function backupSync_data(timestamp, ws) {
|
||||
function backupSync_data(last_time, ws) {
|
||||
const sendTable = (table, id_list) => new Promise((res, rej) => {
|
||||
DB.query(`SELECT * FROM ${table} WHERE id IN (${id_list})`)
|
||||
.then(data => {
|
||||
@ -68,7 +68,7 @@ function backupSync_data(timestamp, ws) {
|
||||
});
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT * FROM _backup WHERE mode=TRUE AND timestamp > ?", [timestamp]).then(result => {
|
||||
DB.query("SELECT * FROM _backup WHERE mode=TRUE AND u_time > ?", [last_time]).then(result => {
|
||||
let sync_needed = {};
|
||||
result.forEach(r => r.t_name in sync_needed ? sync_needed[r.t_name].push(r.id) : sync_needed[r.t_name] = [r.id]);
|
||||
ws.send(JSON.stringify({
|
||||
@ -263,8 +263,5 @@ function tableSync_checksum(tables, ws) {
|
||||
module.exports = {
|
||||
sendBackupData,
|
||||
sendTableHash,
|
||||
sendTableData,
|
||||
set DB(db) {
|
||||
DB = db;
|
||||
}
|
||||
sendTableData
|
||||
}
|
||||
225
src/blockchain.js
Normal file
225
src/blockchain.js
Normal file
@ -0,0 +1,225 @@
|
||||
'use strict';
|
||||
|
||||
const pCode = require('../docs/scripts/floExchangeAPI').processCode;
|
||||
const { collectAndCall } = require('./backup/head');
|
||||
const keys = require('./keys');
|
||||
const DB = require("./database");
|
||||
|
||||
const TYPE_VAULT = "VAULT",
|
||||
TYPE_CONVERT = "CONVERT",
|
||||
TYPE_CONVERT_POOL = "CONVERT_POOL",
|
||||
TYPE_CONVERT_REFUND = "REFUND",
|
||||
TYPE_BLOCKCHAIN_BOND = "BOND",
|
||||
TYPE_BOBS_FUND = "BOB-FUND";
|
||||
|
||||
const SINK_GROUP = {
|
||||
[TYPE_VAULT]: keys.sink_groups.EXCHANGE,
|
||||
[TYPE_CONVERT]: keys.sink_groups.CONVERT,
|
||||
[TYPE_CONVERT_POOL]: keys.sink_groups.CONVERT,
|
||||
[TYPE_CONVERT_REFUND]: keys.sink_groups.CONVERT,
|
||||
[TYPE_BLOCKCHAIN_BOND]: keys.sink_groups.BLOCKCHAIN_BONDS,
|
||||
[TYPE_BOBS_FUND]: keys.sink_groups.BOBS_FUND
|
||||
}
|
||||
|
||||
const balance_locked = {},
|
||||
balance_cache = {},
|
||||
callbackCollection = {
|
||||
[TYPE_VAULT]: {},
|
||||
[TYPE_CONVERT]: {},
|
||||
[TYPE_CONVERT_POOL]: {},
|
||||
[TYPE_CONVERT_REFUND]: {},
|
||||
[TYPE_BLOCKCHAIN_BOND]: {},
|
||||
[TYPE_BOBS_FUND]: {}
|
||||
};
|
||||
|
||||
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(type, quantity, asset, sinkList = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!sinkList)
|
||||
sinkList = keys.sink_chest.list(SINK_GROUP[type]).map(s => [s, s in balance_cache ? balance_cache[s][asset] || 0 : 0]) //TODO: improve sorting
|
||||
.sort((a, b) => b[1] - a[1]).map(x => x[0]);
|
||||
if (!sinkList.length)
|
||||
return reject(`Insufficient balance for asset(${asset}) in chest(${SINK_GROUP[type]})`);
|
||||
let sinkID = sinkList.shift();
|
||||
getBalance(sinkID, asset).then(balance => {
|
||||
if (!(sinkID in balance_cache))
|
||||
balance_cache[sinkID] = {};
|
||||
balance_cache[sinkID][asset] = balance;
|
||||
if (balance > (quantity + (sinkID in balance_locked ? balance_locked[sinkID][asset] || 0 : 0)))
|
||||
return resolve(sinkID);
|
||||
else
|
||||
getSinkID(type, quantity, asset, sinkList)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
getSinkID(type, quantity, asset, sinkList)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
const WITHDRAWAL_MESSAGE = {
|
||||
[TYPE_VAULT]: "(withdrawal from market)",
|
||||
[TYPE_CONVERT]: "(convert coin)",
|
||||
[TYPE_CONVERT_POOL]: "(convert fund)",
|
||||
[TYPE_CONVERT_REFUND]: "(refund from market)",
|
||||
[TYPE_BLOCKCHAIN_BOND]: "(bond closing)",
|
||||
[TYPE_BOBS_FUND]: "(fund investment closing)"
|
||||
}
|
||||
|
||||
function sendTx(floID, asset, quantity, sinkID, sinkKey, message) {
|
||||
switch (asset) {
|
||||
case "FLO":
|
||||
return floBlockchainAPI.sendTx(sinkID, floID, quantity, sinkKey, message);
|
||||
case "BTC":
|
||||
let btc_sinkID = btcOperator.convert.legacy2bech(sinkID),
|
||||
btc_receiver = btcOperator.convert.legacy2bech(floID);
|
||||
return btcOperator.sendTx(btc_sinkID, sinkKey, btc_receiver, quantity, null, { fee_from_receiver: true });
|
||||
default:
|
||||
return floTokenAPI.sendToken(sinkKey, quantity, floID, message, asset);
|
||||
}
|
||||
}
|
||||
|
||||
const updateSyntax = {
|
||||
[TYPE_VAULT]: "UPDATE VaultTransactions SET r_status=?, txid=? WHERE id=?",
|
||||
[TYPE_CONVERT]: "UPDATE DirectConvert SET r_status=?, out_txid=? WHERE id=?",
|
||||
[TYPE_CONVERT_POOL]: "UPDATE ConvertFund SET r_status=?, txid=? WHERE id=?",
|
||||
[TYPE_CONVERT_REFUND]: "UPDATE RefundConvert SET r_status=?, out_txid=? WHERE id=?",
|
||||
[TYPE_BLOCKCHAIN_BOND]: "UPDATE CloseBondTransact SET r_status=?, txid=? WHERE id=?",
|
||||
[TYPE_BOBS_FUND]: "UPDATE CloseFundTransact SET r_status=?, txid=? WHERE id=?"
|
||||
};
|
||||
|
||||
function sendAsset(floID, asset, quantity, type, id) {
|
||||
quantity = global.toStandardDecimal(quantity);
|
||||
getSinkID(type, quantity, asset).then(sinkID => {
|
||||
let callback = (sinkKey) => {
|
||||
//Send asset to user via API
|
||||
sendTx(floID, asset, quantity, sinkID, sinkKey, WITHDRAWAL_MESSAGE[type]).then(txid => {
|
||||
if (!txid)
|
||||
console.error("Transaction not successful");
|
||||
else //Transaction was successful, Add in database
|
||||
DB.query(updateSyntax[type], [pCode.STATUS_CONFIRMATION, txid, id])
|
||||
.then(_ => null).catch(error => console.error(error));
|
||||
}).catch(error => console.error(error)).finally(_ => {
|
||||
delete callbackCollection[type][id];
|
||||
balance_locked[sinkID][asset] -= quantity;
|
||||
});
|
||||
}
|
||||
collectAndCall(sinkID, callback); //TODO: add timeout to prevent infinite wait
|
||||
callbackCollection[type][id] = callback;
|
||||
if (!(sinkID in balance_locked))
|
||||
balance_locked[sinkID] = {};
|
||||
balance_locked[sinkID][asset] = (balance_locked[sinkID][asset] || 0) + quantity;
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function withdrawAsset_init(floID, asset, amount) {
|
||||
amount = global.toStandardDecimal(amount);
|
||||
let asset_type = ["FLO", "BTC"].includes(asset) ? pCode.ASSET_TYPE_COIN : pCode.ASSET_TYPE_TOKEN;
|
||||
DB.query("INSERT INTO VaultTransactions (floID, mode, asset_type, asset, amount, r_status) VALUES (?)", [[floID, pCode.VAULT_MODE_WITHDRAW, asset_type, asset, amount, pCode.STATUS_PENDING]])
|
||||
.then(result => sendAsset(floID, asset, amount, TYPE_VAULT, result.insertId))
|
||||
.catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function withdrawAsset_retry(floID, asset, amount, id) {
|
||||
if (id in callbackCollection[TYPE_VAULT])
|
||||
console.debug("A callback is already pending for this Coin transfer");
|
||||
else sendAsset(floID, asset, amount, TYPE_VAULT, id);
|
||||
}
|
||||
|
||||
function convertToCoin_init(floID, coin, currency_amount, rate, id) {
|
||||
let coin_quantity = global.toStandardDecimal(currency_amount / rate);
|
||||
DB.query("UPDATE DirectConvert SET quantity=?, r_status=?, rate=?, locktime=DEFAULT WHERE id=?", [coin_quantity, pCode.STATUS_PROCESSING, rate, id])
|
||||
.then(result => sendAsset(floID, coin, coin_quantity, TYPE_CONVERT, id))
|
||||
.catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function convertToCoin_retry(floID, coin, coin_quantity, id) {
|
||||
if (id in callbackCollection[TYPE_CONVERT])
|
||||
console.debug("A callback is already pending for this Coin convert");
|
||||
else sendAsset(floID, coin, coin_quantity, TYPE_CONVERT, id);
|
||||
}
|
||||
|
||||
function convertFromCoin_init(floID, coin_quantity, rate, id) {
|
||||
let currency_amount = global.toStandardDecimal(coin_quantity * rate);
|
||||
DB.query("UPDATE DirectConvert SET amount=?, r_status=?, rate=?, locktime=DEFAULT WHERE id=?", [currency_amount, pCode.STATUS_PROCESSING, rate, id])
|
||||
.then(result => sendAsset(floID, floGlobals.currency, currency_amount, TYPE_CONVERT, id))
|
||||
.catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function convertFromCoin_retry(floID, currency_amount, id) {
|
||||
if (id in callbackCollection[TYPE_CONVERT])
|
||||
console.debug("A callback is already pending for this Coin Convert");
|
||||
else sendAsset(floID, floGlobals.currency, currency_amount, TYPE_CONVERT, id);
|
||||
}
|
||||
|
||||
function convertFundWithdraw_retry(asset, amount, id) {
|
||||
if (id in callbackCollection[TYPE_CONVERT_POOL])
|
||||
console.debug("A callback is already pending for this Convert fund withdrawal");
|
||||
else sendAsset(floGlobals.adminID, asset, amount, TYPE_CONVERT_POOL, id);
|
||||
}
|
||||
|
||||
function bondTransact_retry(floID, amount, btc_rate, usd_rate, id) {
|
||||
if (id in callbackCollection[TYPE_BLOCKCHAIN_BOND])
|
||||
console.debug("A callback is already pending for this Bond closing");
|
||||
else sendAsset(floID, "BTC", amount / (btc_rate * usd_rate), TYPE_BLOCKCHAIN_BOND, id);
|
||||
}
|
||||
function fundTransact_retry(floID, amount, btc_rate, usd_rate, id) {
|
||||
if (id in callbackCollection[TYPE_BOBS_FUND])
|
||||
console.debug("A callback is already pending for this Fund investment closing");
|
||||
else sendAsset(floID, "BTC", amount / (btc_rate * usd_rate), TYPE_BOBS_FUND, id);
|
||||
}
|
||||
|
||||
function refundConvert_init(floID, asset, amount, id) {
|
||||
amount = global.toStandardDecimal(amount);
|
||||
DB.query("UPDATE RefundConvert SET amount=?, r_status=?, locktime=DEFAULT WHERE id=?", [amount, pCode.STATUS_PROCESSING, id])
|
||||
.then(result => sendAsset(floID, asset, amount, TYPE_CONVERT_REFUND, id))
|
||||
.catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function refundConvert_retry(floID, asset, amount, id) {
|
||||
if (id in callbackCollection[TYPE_CONVERT_REFUND])
|
||||
console.debug("A callback is already pending for this Refund");
|
||||
else sendAsset(floID, asset, amount, TYPE_CONVERT_REFUND, id);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
withdrawAsset: {
|
||||
init: withdrawAsset_init,
|
||||
retry: withdrawAsset_retry
|
||||
},
|
||||
convertToCoin: {
|
||||
init: convertToCoin_init,
|
||||
retry: convertToCoin_retry
|
||||
},
|
||||
convertFromCoin: {
|
||||
init: convertFromCoin_init,
|
||||
retry: convertFromCoin_retry
|
||||
},
|
||||
convertFundWithdraw: {
|
||||
retry: convertFundWithdraw_retry
|
||||
},
|
||||
bondTransact: {
|
||||
retry: bondTransact_retry
|
||||
},
|
||||
fundTransact: {
|
||||
retry: fundTransact_retry
|
||||
},
|
||||
refundConvert: {
|
||||
init: refundConvert_init,
|
||||
retry: refundConvert_retry
|
||||
}
|
||||
}
|
||||
301
src/coupling.js
301
src/coupling.js
@ -1,121 +1,187 @@
|
||||
'use strict';
|
||||
|
||||
const group = require("./group");
|
||||
const price = require("./price");
|
||||
const DB = require("./database");
|
||||
|
||||
const {
|
||||
WAIT_TIME,
|
||||
TRADE_HASH_PREFIX
|
||||
} = require("./_constants")["market"];
|
||||
|
||||
var DB; //container for database
|
||||
const updateBalance = {};
|
||||
updateBalance.consume = (floID, token, amount) => ["UPDATE UserBalance SET quantity=quantity-? WHERE floID=? AND token=?", [amount, floID, token]];
|
||||
updateBalance.add = (floID, token, amount) => ["INSERT INTO UserBalance (floID, token, quantity) VALUE (?) ON DUPLICATE KEY UPDATE quantity=quantity+?", [[floID, token, amount], amount]];
|
||||
|
||||
function startCouplingForAsset(asset) {
|
||||
price.getRates(asset).then(cur_rate => {
|
||||
cur_rate = cur_rate.toFixed(3);
|
||||
group.getBestPairs(asset, cur_rate)
|
||||
.then(bestPairQueue => processCoupling(bestPairQueue))
|
||||
.catch(error => console.error("initiateCoupling", error))
|
||||
const couplingInstance = {},
|
||||
couplingTimeout = {};
|
||||
|
||||
function stopAllInstance() {
|
||||
for (let asset in couplingTimeout) {
|
||||
if (couplingTimeout[asset])
|
||||
clearTimeout(couplingTimeout[asset]);
|
||||
delete couplingInstance[asset];
|
||||
delete couplingTimeout[asset];
|
||||
}
|
||||
}
|
||||
|
||||
function startCouplingForAsset(asset, updatePrice = false) {
|
||||
if (couplingInstance[asset] === true) { //if coupling is already running for asset
|
||||
if (updatePrice) { //wait until current instance is over
|
||||
if (couplingTimeout[asset]) clearTimeout(couplingTimeout[asset]);
|
||||
couplingTimeout[asset] = setTimeout(() => startCouplingForAsset(asset, true), WAIT_TIME);
|
||||
}
|
||||
return;
|
||||
}
|
||||
price.getRates(asset, updatePrice).then(cur_rate => {
|
||||
cur_rate = global.toStandardDecimal(cur_rate);
|
||||
couplingInstance[asset] = true; //set instance as running
|
||||
recursiveCoupling(asset, cur_rate, updatePrice);
|
||||
}).catch(error => console.error(error));
|
||||
}
|
||||
|
||||
function processCoupling(bestPairQueue) {
|
||||
bestPairQueue.get().then(pair_result => {
|
||||
let buyer_best = pair_result.buyOrder,
|
||||
seller_best = pair_result.sellOrder;
|
||||
//console.debug("Sell:", seller_best);
|
||||
//console.debug("Buy:", buyer_best);
|
||||
spendAsset(bestPairQueue.asset, buyer_best, seller_best, pair_result.null_base).then(spent => {
|
||||
if (!spent.quantity) {
|
||||
//Happens when there are only Null-base assets
|
||||
bestPairQueue.next(spent.quantity, spent.incomplete);
|
||||
processCoupling(bestPairQueue);
|
||||
return;
|
||||
}
|
||||
let txQueries = spent.txQueries;
|
||||
processOrders(seller_best, buyer_best, txQueries, spent.quantity, spent.incomplete && pair_result.null_base);
|
||||
updateBalance(seller_best, buyer_best, txQueries, bestPairQueue.asset, bestPairQueue.cur_rate, spent.quantity);
|
||||
//begin audit
|
||||
beginAudit(seller_best.floID, buyer_best.floID, bestPairQueue.asset, bestPairQueue.cur_rate, spent.quantity).then(audit => {
|
||||
//process txn query in SQL
|
||||
DB.transaction(txQueries).then(_ => {
|
||||
bestPairQueue.next(spent.quantity, spent.incomplete);
|
||||
console.log(`Transaction was successful! BuyOrder:${buyer_best.id}| SellOrder:${seller_best.id}`);
|
||||
audit.end();
|
||||
price.updateLastTime();
|
||||
//Since a tx was successful, match again
|
||||
processCoupling(bestPairQueue);
|
||||
}).catch(error => console.error(error));
|
||||
}).catch(error => console.error(error));
|
||||
}).catch(error => console.error(error));
|
||||
}).catch(error => {
|
||||
let noBuy, noSell;
|
||||
if (error.buy === undefined)
|
||||
noBuy = false;
|
||||
else if (error.buy !== false) {
|
||||
console.error(error.buy);
|
||||
noBuy = null;
|
||||
} else {
|
||||
console.log("No valid buyOrders for Asset:", bestPairQueue.asset);
|
||||
noBuy = true;
|
||||
}
|
||||
if (error.sell === undefined)
|
||||
noSell = false;
|
||||
else if (error.sell !== false) {
|
||||
console.error(error.sell);
|
||||
noSell = null;
|
||||
} else {
|
||||
console.log("No valid sellOrders for Asset:", bestPairQueue.asset);
|
||||
noSell = true;
|
||||
}
|
||||
price.noOrder(bestPairQueue.asset, noBuy, noSell);
|
||||
});
|
||||
}
|
||||
|
||||
function spendAsset(asset, buyOrder, sellOrder, null_base) {
|
||||
function getBestPair(asset, cur_rate) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query('SELECT id, quantity FROM Vault WHERE floID=? AND asset=? AND base IS ' +
|
||||
(null_base ? "NULL ORDER BY locktime" : "NOT NULL ORDER BY base"), [sellOrder.floID, asset]).then(result => {
|
||||
let rem = Math.min(buyOrder.quantity, sellOrder.quantity),
|
||||
txQueries = [];
|
||||
for (let i = 0; i < result.length && rem > 0; i++)
|
||||
if (rem < result[i].quantity) {
|
||||
txQueries.push(["UPDATE Vault SET quantity=quantity-? WHERE id=?", [rem, result[i].id]]);
|
||||
rem = 0;
|
||||
} else {
|
||||
txQueries.push(["DELETE FROM Vault WHERE id=?", [result[i].id]]);
|
||||
rem -= result[i].quantity;
|
||||
}
|
||||
resolve({
|
||||
quantity: Math.min(buyOrder.quantity, sellOrder.quantity) - rem,
|
||||
txQueries,
|
||||
incomplete: rem > 0
|
||||
});
|
||||
}).catch(error => reject(error));
|
||||
Promise.allSettled([getBestBuyer(asset, cur_rate), getBestSeller(asset, cur_rate)]).then(results => {
|
||||
if (results[0].status === "fulfilled" && results[1].status === "fulfilled")
|
||||
resolve({
|
||||
buy: results[0].value,
|
||||
sell: results[1].value,
|
||||
})
|
||||
else
|
||||
reject({
|
||||
buy: results[0].reason,
|
||||
sell: results[1].reason
|
||||
})
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function processOrders(seller_best, buyer_best, txQueries, quantity, clear_sell) {
|
||||
const getBestSeller = (asset, cur_rate) => new Promise((resolve, reject) => {
|
||||
DB.query("SELECT SellOrder.id, SellOrder.floID, SellOrder.quantity, SellChips.id AS chip_id, SellChips.quantity AS chip_quantity FROM SellOrder" +
|
||||
" INNER JOIN UserBalance ON UserBalance.floID = SellOrder.floID AND UserBalance.token = SellOrder.asset" +
|
||||
" INNER JOIN SellChips ON SellChips.floID = SellOrder.floID AND SellChips.asset = SellOrder.asset AND SellChips.base <= ?" +
|
||||
" LEFT JOIN UserTag ON UserTag.floID = SellOrder.floID" +
|
||||
" LEFT JOIN TagList ON TagList.tag = UserTag.tag" +
|
||||
" WHERE UserBalance.quantity >= SellOrder.quantity AND SellOrder.asset = ? AND SellOrder.minPrice <= ?" +
|
||||
" ORDER BY TagList.sellPriority DESC, SellChips.locktime ASC, SellOrder.time_placed ASC" +
|
||||
" LIMIT 1", [cur_rate, asset, cur_rate]
|
||||
).then(result => {
|
||||
if (result.length)
|
||||
resolve(result[0]);
|
||||
else
|
||||
reject(null);
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
|
||||
const getBestBuyer = (asset, cur_rate) => new Promise((resolve, reject) => {
|
||||
DB.query("SELECT BuyOrder.id, BuyOrder.floID, BuyOrder.quantity FROM BuyOrder" +
|
||||
" INNER JOIN UserBalance ON UserBalance.floID = BuyOrder.floID AND UserBalance.token = ?" +
|
||||
" LEFT JOIN UserTag ON UserTag.floID = BuyOrder.floID" +
|
||||
" LEFT JOIN TagList ON TagList.tag = UserTag.tag" +
|
||||
" WHERE UserBalance.quantity >= BuyOrder.maxPrice * BuyOrder.quantity AND BuyOrder.asset = ? AND BuyOrder.maxPrice >= ?" +
|
||||
" ORDER BY TagList.buyPriority DESC, BuyOrder.time_placed ASC" +
|
||||
" LIMIT 1", [floGlobals.currency, asset, cur_rate]
|
||||
).then(result => {
|
||||
if (result.length)
|
||||
resolve(result[0]);
|
||||
else
|
||||
reject(null);
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
|
||||
function recursiveCoupling(asset, cur_rate, flag = false) {
|
||||
processCoupling(asset, cur_rate).then(result => {
|
||||
console.log(result);
|
||||
if (couplingInstance[asset] === true)
|
||||
recursiveCoupling(asset, cur_rate, true);
|
||||
}).catch(error => {
|
||||
//noBuy = error[0], noSell = error[1], reason = error[2]
|
||||
price.noOrder(asset, error[0], error[1]);
|
||||
error[3] ? console.debug(error[2]) : console.error(error[2]);
|
||||
//set timeout for next coupling (if not order placement occurs)
|
||||
if (flag) {
|
||||
price.updateLastTime(asset);
|
||||
if (couplingInstance[asset] === true && flag) {
|
||||
//if price was updated and/or trade happened, reset timer
|
||||
if (couplingTimeout[asset]) clearTimeout(couplingTimeout[asset]);
|
||||
couplingTimeout[asset] = setTimeout(() => startCouplingForAsset(asset, true), price.MIN_TIME);
|
||||
}
|
||||
}
|
||||
delete couplingInstance[asset];
|
||||
})
|
||||
}
|
||||
|
||||
function processCoupling(asset, cur_rate) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getBestPair(asset, cur_rate).then(best => {
|
||||
//console.debug("Sell:", best.sell);
|
||||
//console.debug("Buy:", best.buy);
|
||||
let quantity = Math.min(best.buy.quantity, best.sell.quantity, best.sell.chip_quantity);
|
||||
let txQueries = processOrders(best.sell, best.buy, asset, cur_rate, quantity);
|
||||
//begin audit
|
||||
beginAudit(best.sell.floID, best.buy.floID, asset, cur_rate, quantity).then(audit => {
|
||||
//process txn query in SQL
|
||||
DB.transaction(txQueries).then(_ => {
|
||||
audit.end();
|
||||
resolve(`Transaction was successful! BuyOrder:${best.buy.id}| SellOrder:${best.sell.id}`)
|
||||
}).catch(error => reject([null, null, error]));
|
||||
}).catch(error => reject([null, null, error]));
|
||||
}).catch(error => {
|
||||
let noBuy, noSell;
|
||||
if (error.buy === undefined)
|
||||
noBuy = false;
|
||||
else if (error.buy === null)
|
||||
noBuy = true;
|
||||
else {
|
||||
console.error(error.buy);
|
||||
noBuy = null;
|
||||
}
|
||||
if (error.sell === undefined)
|
||||
noSell = false;
|
||||
else if (error.sell === null)
|
||||
noSell = true;
|
||||
else {
|
||||
console.error(error.sell);
|
||||
noSell = null;
|
||||
}
|
||||
reject([noBuy, noSell, `No valid ${noSell ? 'sellOrders' : ''} | ${noBuy ? 'buyOrders' : ''} for Asset: ${asset}`, true]);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function processOrders(seller_best, buyer_best, asset, cur_rate, quantity) {
|
||||
let txQueries = [];
|
||||
if (quantity > buyer_best.quantity || quantity > seller_best.quantity)
|
||||
throw Error("Tx quantity cannot be more than order quantity");
|
||||
|
||||
//Process Buy Order
|
||||
if (quantity == buyer_best.quantity)
|
||||
txQueries.push(["DELETE FROM BuyOrder WHERE id=?", [buyer_best.id]]);
|
||||
else
|
||||
txQueries.push(["UPDATE BuyOrder SET quantity=quantity-? WHERE id=?", [quantity, buyer_best.id]]);
|
||||
|
||||
//Process Sell Order
|
||||
if (quantity == seller_best.quantity || clear_sell) //clear_sell must be true iff an order is placed without enough Asset
|
||||
if (quantity == seller_best.quantity)
|
||||
txQueries.push(["DELETE FROM SellOrder WHERE id=?", [seller_best.id]]);
|
||||
else
|
||||
txQueries.push(["UPDATE SellOrder SET quantity=quantity-? WHERE id=?", [quantity, seller_best.id]]);
|
||||
}
|
||||
|
||||
function updateBalance(seller_best, buyer_best, txQueries, asset, cur_price, quantity) {
|
||||
//Update cash balance for seller and buyer
|
||||
let totalAmount = cur_price * quantity;
|
||||
txQueries.push(["INSERT INTO Cash (floID, balance) VALUE (?, ?) ON DUPLICATE KEY UPDATE balance=balance+?", [seller_best.floID, totalAmount, totalAmount]]);
|
||||
txQueries.push(["UPDATE Cash SET balance=balance-? WHERE floID=?", [totalAmount, buyer_best.floID]]);
|
||||
//Add coins to Buyer
|
||||
txQueries.push(["INSERT INTO Vault(floID, asset, base, quantity) VALUES (?, ?, ?, ?)", [buyer_best.floID, asset, cur_price, quantity]])
|
||||
//Process Sell Chip
|
||||
if (quantity == seller_best.chip_quantity)
|
||||
txQueries.push(["DELETE FROM SellChips WHERE id=?", [seller_best.chip_id]]);
|
||||
else
|
||||
txQueries.push(["UPDATE SellChips SET quantity=quantity-? WHERE id=?", [quantity, seller_best.chip_id]]);
|
||||
|
||||
//Update cash/asset balance for seller and buyer
|
||||
let totalAmount = cur_rate * quantity;
|
||||
txQueries.push(updateBalance.add(seller_best.floID, floGlobals.currency, totalAmount));
|
||||
txQueries.push(updateBalance.consume(buyer_best.floID, floGlobals.currency, totalAmount));
|
||||
txQueries.push(updateBalance.consume(seller_best.floID, asset, quantity));
|
||||
txQueries.push(updateBalance.add(buyer_best.floID, asset, quantity));
|
||||
|
||||
//Add SellChips to Buyer
|
||||
txQueries.push(["INSERT INTO SellChips(floID, asset, base, quantity) VALUES (?)", [[buyer_best.floID, asset, cur_rate, quantity]]])
|
||||
|
||||
//Record transaction
|
||||
let time = Date.now();
|
||||
let hash = TRADE_HASH_PREFIX + Crypto.SHA256(JSON.stringify({
|
||||
@ -123,13 +189,15 @@ function updateBalance(seller_best, buyer_best, txQueries, asset, cur_price, qua
|
||||
buyer: buyer_best.floID,
|
||||
asset: asset,
|
||||
quantity: quantity,
|
||||
unitValue: cur_price,
|
||||
unitValue: cur_rate,
|
||||
tx_time: time,
|
||||
}));
|
||||
txQueries.push([
|
||||
"INSERT INTO TradeTransactions (seller, buyer, asset, quantity, unitValue, tx_time, txid) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
[seller_best.floID, buyer_best.floID, asset, quantity, cur_price, global.convertDateToString(time), hash]
|
||||
"INSERT INTO TradeTransactions (seller, buyer, asset, quantity, unitValue, tx_time, txid) VALUES (?)",
|
||||
[[seller_best.floID, buyer_best.floID, asset, quantity, cur_rate, new Date(time), hash]]
|
||||
]);
|
||||
|
||||
return txQueries;
|
||||
}
|
||||
|
||||
function beginAudit(sellerID, buyerID, asset, unit_price, quantity) {
|
||||
@ -145,47 +213,40 @@ function endAudit(sellerID, buyerID, asset, old_bal, unit_price, quantity) {
|
||||
DB.query("INSERT INTO AuditTrade (asset, quantity, unit_price, total_cost," +
|
||||
" sellerID, seller_old_cash, seller_old_asset, seller_new_cash, seller_new_asset," +
|
||||
" buyerID, buyer_old_cash, buyer_old_asset, buyer_new_cash, buyer_new_asset)" +
|
||||
" Value (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
||||
" Value (?)", [[
|
||||
asset, quantity, unit_price, quantity * unit_price,
|
||||
sellerID, old_bal[sellerID].cash, old_bal[sellerID].asset, new_bal[sellerID].cash, new_bal[sellerID].asset,
|
||||
buyerID, old_bal[buyerID].cash, old_bal[buyerID].asset, new_bal[buyerID].cash, new_bal[buyerID].asset,
|
||||
]).then(_ => null).catch(error => console.error(error))
|
||||
]]).then(_ => null).catch(error => console.error(error))
|
||||
}).catch(error => console.error(error));
|
||||
}
|
||||
|
||||
function auditBalance(sellerID, buyerID, asset) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let balance = {
|
||||
[sellerID]: {},
|
||||
[buyerID]: {}
|
||||
[sellerID]: {
|
||||
cash: 0,
|
||||
asset: 0
|
||||
},
|
||||
[buyerID]: {
|
||||
cash: 0,
|
||||
asset: 0
|
||||
}
|
||||
};
|
||||
DB.query("SELECT floID, balance FROM Cash WHERE floID IN (?, ?)", [sellerID, buyerID]).then(result => {
|
||||
for (let i in result)
|
||||
balance[result[i].floID].cash = result[i].balance;
|
||||
DB.query("SELECT floID, SUM(quantity) as asset_balance FROM Vault WHERE asset=? AND floID IN (?, ?) GROUP BY floID", [asset, sellerID, buyerID]).then(result => {
|
||||
for (let i in result)
|
||||
balance[result[i].floID].asset = result[i].asset_balance;
|
||||
//Set them as 0 if undefined or null
|
||||
balance[sellerID].cash = balance[sellerID].cash || 0;
|
||||
balance[sellerID].asset = balance[sellerID].asset || 0;
|
||||
balance[buyerID].cash = balance[buyerID].cash || 0;
|
||||
balance[buyerID].asset = balance[buyerID].asset || 0;
|
||||
resolve(balance);
|
||||
}).catch(error => reject(error))
|
||||
DB.query("SELECT floID, quantity, token FROM UserBalance WHERE floID IN (?) AND token IN (?)", [[sellerID, buyerID], [floGlobals.currency, asset]]).then(result => {
|
||||
for (let i in result) {
|
||||
if (result[i].token === floGlobals.currency)
|
||||
balance[result[i].floID].cash = result[i].quantity;
|
||||
else if (result[i].token === asset)
|
||||
balance[result[i].floID].asset = result[i].quantity;
|
||||
}
|
||||
resolve(balance);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initiate: startCouplingForAsset,
|
||||
group: {
|
||||
addTag: group.addTag,
|
||||
removeTag: group.removeTag
|
||||
},
|
||||
price,
|
||||
set DB(db) {
|
||||
DB = db;
|
||||
group.DB = db;
|
||||
price.DB = db;
|
||||
}
|
||||
stopAll: stopAllInstance,
|
||||
updateBalance
|
||||
}
|
||||
171
src/database.js
171
src/database.js
@ -1,99 +1,104 @@
|
||||
'use strict';
|
||||
var mysql = require('mysql');
|
||||
|
||||
function Database(user, password, dbname, host = 'localhost') {
|
||||
const db = {};
|
||||
|
||||
Object.defineProperty(db, "connect", {
|
||||
get: () => new Promise((resolve, reject) => {
|
||||
db.pool.getConnection((error, conn) => {
|
||||
if (error)
|
||||
reject(error);
|
||||
else
|
||||
resolve(conn);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
Object.defineProperty(db, "query", {
|
||||
value: (sql, values) => new Promise((resolve, reject) => {
|
||||
db.connect.then(conn => {
|
||||
const fn = (err, res) => {
|
||||
conn.release();
|
||||
(err ? reject(err) : resolve(res));
|
||||
};
|
||||
if (values)
|
||||
conn.query(sql, values, fn);
|
||||
else
|
||||
conn.query(sql, fn);
|
||||
}).catch(error => reject(error));
|
||||
})
|
||||
});
|
||||
|
||||
Object.defineProperty(db, "transaction", {
|
||||
value: (queries) => new Promise((resolve, reject) => {
|
||||
db.connect.then(conn => {
|
||||
conn.beginTransaction(err => {
|
||||
if (err)
|
||||
conn.rollback(() => {
|
||||
conn.release();
|
||||
reject(err);
|
||||
});
|
||||
else {
|
||||
(function queryFn(result) {
|
||||
if (!queries.length) {
|
||||
conn.commit(err => {
|
||||
if (err)
|
||||
conn.rollback(() => {
|
||||
conn.release();
|
||||
reject(err);
|
||||
});
|
||||
else {
|
||||
conn.release();
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let q_i = queries.shift();
|
||||
const callback = function(err, res) {
|
||||
if (err)
|
||||
conn.rollback(() => {
|
||||
conn.release();
|
||||
reject(err);
|
||||
});
|
||||
else {
|
||||
result.push(res);
|
||||
queryFn(result);
|
||||
}
|
||||
};
|
||||
if (!Array.isArray(q_i))
|
||||
q_i = [q_i];
|
||||
if (q_i[1])
|
||||
conn.query(q_i[0], q_i[1], callback);
|
||||
else
|
||||
conn.query(q_i[0], callback);
|
||||
}
|
||||
})([]);
|
||||
}
|
||||
});
|
||||
}).catch(error => reject(error));
|
||||
})
|
||||
});
|
||||
var pool;//container for connected pool;
|
||||
|
||||
function connectToDatabase(user, password, dbname, host = 'localhost') {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.pool = mysql.createPool({
|
||||
pool = mysql.createPool({
|
||||
host: host,
|
||||
user: user,
|
||||
password: password,
|
||||
database: dbname,
|
||||
//dateStrings : true,
|
||||
timezone: 'UTC'
|
||||
//timezone: 'UTC'
|
||||
});
|
||||
db.connect.then(conn => {
|
||||
getConnection().then(conn => {
|
||||
conn.release();
|
||||
resolve(db);
|
||||
resolve(pool);
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = Database;
|
||||
function getConnection() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!pool)
|
||||
return reject("Database not connected");
|
||||
pool.getConnection((error, conn) => {
|
||||
if (error)
|
||||
reject(error);
|
||||
else
|
||||
resolve(conn);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function SQL_query(sql, values) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getConnection().then(conn => {
|
||||
const fn = (err, res) => {
|
||||
conn.release();
|
||||
(err ? reject(err) : resolve(res));
|
||||
};
|
||||
if (values)
|
||||
conn.query(sql, values, fn);
|
||||
else
|
||||
conn.query(sql, fn);
|
||||
}).catch(error => reject(error));
|
||||
})
|
||||
}
|
||||
|
||||
function SQL_transaction(queries) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getConnection().then(conn => {
|
||||
conn.beginTransaction(err => {
|
||||
if (err)
|
||||
conn.rollback(() => {
|
||||
conn.release();
|
||||
reject(err);
|
||||
});
|
||||
else {
|
||||
(function queryFn(result) {
|
||||
if (!queries.length) {
|
||||
conn.commit(err => {
|
||||
if (err)
|
||||
conn.rollback(() => {
|
||||
conn.release();
|
||||
reject(err);
|
||||
});
|
||||
else {
|
||||
conn.release();
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let q_i = queries.shift();
|
||||
const callback = function (err, res) {
|
||||
if (err)
|
||||
conn.rollback(() => {
|
||||
conn.release();
|
||||
reject(err);
|
||||
});
|
||||
else {
|
||||
result.push(res);
|
||||
queryFn(result);
|
||||
}
|
||||
};
|
||||
if (!Array.isArray(q_i))
|
||||
q_i = [q_i];
|
||||
if (q_i[1])
|
||||
conn.query(q_i[0], q_i[1], callback);
|
||||
else
|
||||
conn.query(q_i[0], callback);
|
||||
}
|
||||
})([]);
|
||||
}
|
||||
});
|
||||
}).catch(error => reject(error));
|
||||
})
|
||||
}
|
||||
module.exports = {
|
||||
connect: connectToDatabase,
|
||||
query: SQL_query,
|
||||
transaction: SQL_transaction
|
||||
};
|
||||
385
src/group.js
385
src/group.js
@ -1,385 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var DB; //container for database
|
||||
|
||||
function addTag(floID, tag) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("INSERT INTO UserTag (floID, tag) VALUE (?,?)", [floID, tag])
|
||||
.then(result => resolve(`Added ${floID} to ${tag}`))
|
||||
.catch(error => {
|
||||
if (error.code === "ER_DUP_ENTRY")
|
||||
reject(INVALID(`${floID} already in ${tag}`));
|
||||
else if (error.code === "ER_NO_REFERENCED_ROW")
|
||||
reject(INVALID(`Invalid user-floID and/or Tag`));
|
||||
else
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function removeTag(floID, tag) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("DELETE FROM UserTag WHERE floID=? AND tag=?", [floID, tag])
|
||||
.then(result => resolve(`Removed ${floID} from ${tag}`))
|
||||
.catch(error => reject(error));
|
||||
})
|
||||
}
|
||||
|
||||
function getBestPairs(asset, cur_rate) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT tag, sellPriority, buyPriority FROM TagList").then(result => {
|
||||
//Sorted in Ascending (ie, stack; pop for highest)
|
||||
let tags_buy = result.sort((a, b) => a.buyPriority > b.buyPriority ? 1 : -1).map(r => r.tag);
|
||||
let tags_sell = result.sort((a, b) => a.sellPriority > b.sellPriority ? 1 : -1).map(r => r.tag);
|
||||
resolve(new bestPair(asset, cur_rate, tags_buy, tags_sell));
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
const bestPair = function(asset, cur_rate, tags_buy, tags_sell) {
|
||||
|
||||
Object.defineProperty(this, 'asset', {
|
||||
get: () => asset
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'cur_rate', {
|
||||
get: () => cur_rate,
|
||||
});
|
||||
|
||||
this.get = () => new Promise((resolve, reject) => {
|
||||
Promise.allSettled([getBuyOrder(), getSellOrder()]).then(results => {
|
||||
if (results[0].status === "fulfilled" && results[1].status === "fulfilled")
|
||||
resolve({
|
||||
buyOrder: results[0].value,
|
||||
sellOrder: results[1].value,
|
||||
null_base: getSellOrder.cache.mode_null
|
||||
})
|
||||
else
|
||||
reject({
|
||||
buy: results[0].reason,
|
||||
sell: results[1].reason
|
||||
})
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
|
||||
this.next = (tx_quantity, incomplete_sell) => {
|
||||
let buy = getBuyOrder.cache,
|
||||
sell = getSellOrder.cache;
|
||||
if (buy.cur_order && sell.cur_order) {
|
||||
//buy order
|
||||
if (tx_quantity < buy.cur_order.quantity)
|
||||
buy.cur_order.quantity -= tx_quantity;
|
||||
else if (tx_quantity == buy.cur_order.quantity)
|
||||
buy.cur_order = null;
|
||||
else
|
||||
throw Error("Tx quantity cannot be more than order quantity");
|
||||
//sell order
|
||||
if (tx_quantity < sell.cur_order.quantity) {
|
||||
sell.cur_order.quantity -= tx_quantity;
|
||||
if (incomplete_sell) {
|
||||
if (!sell.mode_null)
|
||||
sell.null_queue.push(sell.cur_order);
|
||||
sell.cur_order = null;
|
||||
}
|
||||
} else if (tx_quantity == sell.cur_order.quantity)
|
||||
sell.cur_order = null;
|
||||
else
|
||||
throw Error("Tx quantity cannot be more than order quantity");
|
||||
} else
|
||||
throw Error("No current order found");
|
||||
};
|
||||
|
||||
const getSellOrder = () => new Promise((resolve, reject) => {
|
||||
let cache = getSellOrder.cache;
|
||||
if (cache.cur_order) { //If cache already has a pending order
|
||||
verifySellOrder(cache.cur_order, asset, cur_rate, cache.mode_null).then(result => {
|
||||
cache.cur_order = result;
|
||||
resolve(result);
|
||||
}).catch(error => {
|
||||
if (error !== false)
|
||||
return reject(error);
|
||||
//Order not valid (minimum gain)
|
||||
cache.cur_order = null;
|
||||
getSellOrder()
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
} else if (cache.orders && cache.orders.length) { //If cache already has orders in priority
|
||||
getTopValidSellOrder(cache.orders, asset, cur_rate, cache.mode_null).then(result => {
|
||||
cache.cur_order = result;
|
||||
resolve(result);
|
||||
}).catch(error => {
|
||||
if (error !== false)
|
||||
return reject(error);
|
||||
//No valid order found in current tag
|
||||
cache.orders = null;
|
||||
getSellOrder()
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
} else if (cache.tags.length) { //If cache has remaining tags
|
||||
cache.cur_tag = cache.tags.pop();
|
||||
getSellOrdersInTag(cache.cur_tag, asset, cur_rate).then(orders => {
|
||||
cache.orders = orders;
|
||||
getSellOrder()
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error));
|
||||
} else if (!cache.end) { //Un-tagged floID's orders (do only once)
|
||||
getUntaggedSellOrders(asset, cur_rate).then(orders => {
|
||||
cache.orders = orders;
|
||||
cache.cur_tag = null;
|
||||
cache.end = true;
|
||||
getSellOrder()
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error));
|
||||
} else if (!cache.mode_null) { //Lowest priority Assets (Brought from other sources)
|
||||
cache.orders = cache.null_queue.reverse(); //Reverse it so that we can pop the highest priority
|
||||
cache.mode_null = true;
|
||||
cache.null_queue = null;
|
||||
getSellOrder()
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
} else
|
||||
reject(false);
|
||||
});
|
||||
getSellOrder.cache = {
|
||||
tags: tags_sell,
|
||||
null_queue: [],
|
||||
mode_null: false
|
||||
};
|
||||
|
||||
const getBuyOrder = () => new Promise((resolve, reject) => {
|
||||
let cache = getBuyOrder.cache;
|
||||
if (cache.cur_order) { //If cache already has a pending order
|
||||
verifyBuyOrder(cache.cur_order, cur_rate).then(result => {
|
||||
cache.cur_order = result;
|
||||
resolve(result);
|
||||
}).catch(error => {
|
||||
if (error !== false)
|
||||
return reject(error);
|
||||
//Order not valid (cash not available)
|
||||
cache.cur_order = null;
|
||||
getBuyOrder()
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
} else if (cache.orders && cache.orders.length) { //If cache already has orders in priority
|
||||
getTopValidBuyOrder(cache.orders, cur_rate).then(result => {
|
||||
cache.cur_order = result;
|
||||
resolve(result);
|
||||
}).catch(error => {
|
||||
if (error !== false)
|
||||
return reject(error);
|
||||
//No valid order found in current tag
|
||||
cache.orders = null;
|
||||
getBuyOrder()
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
} else if (cache.tags.length) { //If cache has remaining tags
|
||||
cache.cur_tag = cache.tags.pop();
|
||||
getBuyOrdersInTag(cache.cur_tag, asset, cur_rate).then(orders => {
|
||||
cache.orders = orders;
|
||||
getBuyOrder()
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error));
|
||||
} else if (!cache.end) { //Un-tagged floID's orders (do only once)
|
||||
getUntaggedBuyOrders(asset, cur_rate).then(orders => {
|
||||
cache.orders = orders;
|
||||
cache.cur_tag = null;
|
||||
cache.end = true;
|
||||
getBuyOrder()
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error));
|
||||
} else
|
||||
reject(false);
|
||||
});
|
||||
getBuyOrder.cache = {
|
||||
tags: tags_buy
|
||||
};
|
||||
}
|
||||
|
||||
function getUntaggedSellOrders(asset, cur_price) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT SellOrder.id, SellOrder.floID, SellOrder.quantity FROM SellOrder" +
|
||||
" LEFT JOIN UserTag ON UserTag.floID = SellOrder.floID" +
|
||||
" WHERE UserTag.floID IS NULL AND SellOrder.asset = ? AND SellOrder.minPrice <=?" +
|
||||
" ORDER BY SellOrder.time_placed", [asset, cur_price])
|
||||
.then(orders => resolve(orders))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function getUntaggedBuyOrders(asset, cur_price) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT BuyOrder.id, BuyOrder.floID, BuyOrder.quantity FROM BuyOrder" +
|
||||
" LEFT JOIN UserTag ON UserTag.floID = BuyOrder.floID" +
|
||||
" WHERE UserTag.floID IS NULL AND BuyOrder.asset = ? AND BuyOrder.maxPrice >=? " +
|
||||
" ORDER BY BuyOrder.time_placed", [asset, cur_price])
|
||||
.then(orders => resolve(orders))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function getSellOrdersInTag(tag, asset, cur_price) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT SellOrder.id, SellOrder.floID, SellOrder.quantity FROM SellOrder" +
|
||||
" INNER JOIN UserTag ON UserTag.floID = SellOrder.floID" +
|
||||
" WHERE UserTag.tag = ? AND SellOrder.asset = ? AND SellOrder.minPrice <=?" +
|
||||
" ORDER BY SellOrder.time_placed", [tag, asset, cur_price]).then(orders => {
|
||||
if (orders.length <= 1) // No (or) Only-one order, hence priority sort not required.
|
||||
resolve(orders);
|
||||
else
|
||||
getPointsFromAPI(tag, orders.map(o => o.floID)).then(points => {
|
||||
let orders_sorted = orders.map(o => [o, points[o.floID]])
|
||||
.sort((a, b) => a[1] > b[1] ? -1 : a[1] < b[1] ? 1 : 0) //sort by points (ascending)
|
||||
.map(x => x[0]);
|
||||
resolve(orders_sorted);
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
|
||||
function getBuyOrdersInTag(tag, asset, cur_price) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT BuyOrder.id, BuyOrder.floID, BuyOrder.quantity FROM BuyOrder" +
|
||||
" INNER JOIN UserTag ON UserTag.floID = BuyOrder.floID" +
|
||||
" WHERE UserTag.tag = ? AND BuyOrder.asset = ? AND BuyOrder.maxPrice >=?" +
|
||||
" ORDER BY BuyOrder.time_placed", [tag, asset, cur_price]).then(orders => {
|
||||
if (orders.length <= 1) // No (or) Only-one order, hence priority sort not required.
|
||||
resolve(orders);
|
||||
else
|
||||
getPointsFromAPI(tag, orders.map(o => o.floID)).then(points => {
|
||||
let orders_sorted = orders.map(o => [o, points[o.floID]])
|
||||
.sort((a, b) => a[1] > b[1] ? -1 : a[1] < b[1] ? 1 : 0) //sort by points (ascending)
|
||||
.map(x => x[0]);
|
||||
resolve(orders_sorted);
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
|
||||
function getPointsFromAPI(tag, floIDs) {
|
||||
floIDs = Array.from(new Set(floIDs));
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT api FROM TagList WHERE tag=?", [tag]).then(result => {
|
||||
let api = result[0].api;
|
||||
Promise.allSettled(floIDs.map(id => fetch_api(api, id))).then(result => {
|
||||
let points = {};
|
||||
for (let i in result)
|
||||
points[floIDs[i]] = result[i].status === "fulfilled" ? result[i].value : 0;
|
||||
resolve(points);
|
||||
})
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
|
||||
function fetch_api(api, id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
//TODO: fetch data from API
|
||||
let url = api.replace('<flo-id>', id);
|
||||
global.fetch(url).then(response => {
|
||||
if (response.ok)
|
||||
response.text()
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
else
|
||||
reject(response);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function getTopValidSellOrder(orders, asset, cur_price, mode_null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!orders.length)
|
||||
return reject(false)
|
||||
verifySellOrder(orders.pop(), asset, cur_price, mode_null) //pop: as the orders are sorted in ascending (highest point should be checked 1st)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => {
|
||||
if (error !== false)
|
||||
return reject(error);
|
||||
getTopValidSellOrder(orders, asset, cur_price, mode_null)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function verifySellOrder(sellOrder, asset, cur_price, mode_null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!mode_null)
|
||||
DB.query("SELECT quantity, base FROM Vault WHERE floID=? AND asset=? AND base IS NOT NULL ORDER BY base", [sellOrder.floID, asset]).then(result => {
|
||||
let rem = sellOrder.quantity,
|
||||
sell_base = 0,
|
||||
base_quantity = 0;
|
||||
for (let i = 0; i < result.length && rem > 0; i++) {
|
||||
if (rem < result[i].quantity) {
|
||||
sell_base += (rem * result[i].base);
|
||||
base_quantity += rem;
|
||||
rem = 0;
|
||||
} else {
|
||||
sell_base += (result[i].quantity * result[i].base);
|
||||
base_quantity += result[i].quantity;
|
||||
rem -= result[i].quantity;
|
||||
}
|
||||
}
|
||||
if (base_quantity)
|
||||
sell_base = sell_base / base_quantity;
|
||||
if (sell_base > cur_price)
|
||||
reject(false);
|
||||
else
|
||||
resolve(sellOrder);
|
||||
}).catch(error => reject(error));
|
||||
else if (mode_null)
|
||||
DB.query("SELECT IFNULL(SUM(quantity), 0) as total FROM Vault WHERE floID=? AND asset=?", [sellOrder.floID, asset]).then(result => {
|
||||
if (result[0].total < sellOrder.quantity)
|
||||
console.warn(`Sell Order ${sellOrder.id} was made without enough Assets. This should not happen`);
|
||||
if (result[0].total > 0)
|
||||
resolve(sellOrder);
|
||||
else
|
||||
reject(false);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function getTopValidBuyOrder(orders, cur_price) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!orders.length)
|
||||
return reject(false)
|
||||
verifyBuyOrder(orders.pop(), cur_price) //pop: as the orders are sorted in ascending (highest point should be checked 1st)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => {
|
||||
if (error !== false)
|
||||
return reject(error);
|
||||
getTopValidBuyOrder(orders, cur_price)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function verifyBuyOrder(buyOrder, cur_price) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT balance FROM Cash WHERE floID=?", [buyOrder.floID]).then(result => {
|
||||
if (!result.length || result[0].balance < cur_price * buyOrder.quantity) {
|
||||
//This should not happen unless a buy order is placed when user doesnt have enough cash balance
|
||||
console.warn(`Buy order ${buyOrder.id} is active, but Cash is insufficient`);
|
||||
reject(false);
|
||||
} else
|
||||
resolve(buyOrder);
|
||||
}).catch(error => reject(error));
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addTag,
|
||||
removeTag,
|
||||
getBestPairs,
|
||||
set DB(db) {
|
||||
DB = db;
|
||||
}
|
||||
};
|
||||
442
src/keys.js
Normal file
442
src/keys.js
Normal file
@ -0,0 +1,442 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const lockfile = require('proper-lockfile');
|
||||
const DB = require("./database");
|
||||
|
||||
var _I = ""; //Instance support
|
||||
for (let arg of process.argv)
|
||||
if (/^-I=/.test(arg)) {
|
||||
_I = arg.split(/=(.*)/s)[1];
|
||||
break;
|
||||
}
|
||||
|
||||
const {
|
||||
SHARES_PER_NODE,
|
||||
SHARE_THRESHOLD,
|
||||
SHUFFLE_INTERVAL
|
||||
} = require("./_constants")["keys"];
|
||||
|
||||
const PRIV_EKEY_MIN = 32,
|
||||
PRIV_EKEY_MAX = 48,
|
||||
PRIME_FILE_TYPE = 'binary',
|
||||
INDEX_FILE_TYPE = 'utf-8',
|
||||
UNSIGNED_INT_MIN = 0,
|
||||
UNSIGNED_INT_MAX = 4294967295,
|
||||
INDEX_FILE_NAME_LENGTH = 16,
|
||||
INDEX_FILE_EXT = '.txt',
|
||||
MIN_DUMMY_FILES = 16,
|
||||
MAX_DUMMY_FILES = 24,
|
||||
MIN_DUMMY_SIZE_MUL = 0.5,
|
||||
MAX_DUMMY_SIZE_MUL = 1.5,
|
||||
SIZE_FACTOR = 100,
|
||||
LOCK_RETRY_MIN_TIME = 1 * 1000,
|
||||
LOCK_RETRY_MAX_TIME = 2 * 1000;
|
||||
|
||||
var node_priv, e_key, node_id, node_pub; //containers for node-key wrapper
|
||||
const _x = {
|
||||
get node_priv() {
|
||||
if (!node_priv || !e_key)
|
||||
throw Error("keys not set");
|
||||
return Crypto.AES.decrypt(node_priv, e_key);
|
||||
},
|
||||
set node_priv(key) {
|
||||
node_pub = floCrypto.getPubKeyHex(key);
|
||||
node_id = floCrypto.getFloID(node_pub);
|
||||
if (!key || !node_pub || !node_id)
|
||||
throw Error("Invalid Keys");
|
||||
let n = floCrypto.randInt(PRIV_EKEY_MIN, PRIV_EKEY_MAX)
|
||||
e_key = floCrypto.randString(n);
|
||||
node_priv = Crypto.AES.encrypt(key, e_key);
|
||||
},
|
||||
args_dir: path.resolve(__dirname, '..', 'args'),
|
||||
get index_dir() {
|
||||
return path.join(this.args_dir, `indexes${_I}`)
|
||||
},
|
||||
get prime_file() {
|
||||
return path.join(this.args_dir, `prime_index${_I}.b`)
|
||||
},
|
||||
get index_file() {
|
||||
try {
|
||||
let data = fs.readFileSync(this.prime_file, PRIME_FILE_TYPE),
|
||||
fname = Crypto.AES.decrypt(data, this.node_priv);
|
||||
return path.join(this.index_dir, fname + INDEX_FILE_EXT);
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
throw Error("Prime-Index Missing/Corrupted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(_x.prime_file, PRIME_FILE_TYPE, (err, res) => {
|
||||
var data, cur_filename, new_filename, priv_key;
|
||||
try {
|
||||
priv_key = _x.node_priv;
|
||||
} catch (error) {
|
||||
return reject(error);
|
||||
}
|
||||
if (!err) {
|
||||
if (res.length) { //prime file not empty
|
||||
try {
|
||||
cur_filename = Crypto.AES.decrypt(res, priv_key);
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
return reject("Prime file corrupted");
|
||||
} try { //read data from index file
|
||||
let tmp = fs.readFileSync(path.join(_x.index_dir, cur_filename + INDEX_FILE_EXT), INDEX_FILE_TYPE);
|
||||
tmp = Crypto.AES.decrypt(tmp, priv_key);
|
||||
JSON.parse(tmp); //check if data is JSON parse-able
|
||||
data = tmp;
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
return reject("Index file corrupted");
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (!fs.existsSync(_x.index_dir)) {
|
||||
fs.mkdirSync(_x.index_dir);
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
return reject("Index directory creation failed");
|
||||
}
|
||||
try { //delete all old dummy files
|
||||
let files = fs.readdirSync(_x.index_dir);
|
||||
for (const file of files)
|
||||
if (!cur_filename || file !== cur_filename + INDEX_FILE_EXT) //check if file is current file
|
||||
fs.unlinkSync(path.join(_x.index_dir, file));
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
return reject("Clear index directory failed");
|
||||
} try { //create files (dummy and new index file)
|
||||
let N = floCrypto.randInt(MIN_DUMMY_FILES, MAX_DUMMY_FILES),
|
||||
k = floCrypto.randInt(0, N);
|
||||
if (typeof data === 'undefined' || data.length == 0) //no existing data, initialize
|
||||
data = JSON.stringify({});
|
||||
let data_size = data.length;
|
||||
for (let i = 0; i <= N; i++) {
|
||||
let f_data, f_name = floCrypto.randString(INDEX_FILE_NAME_LENGTH);
|
||||
if (i == k) {
|
||||
new_filename = f_name;
|
||||
f_data = data;
|
||||
} else {
|
||||
let d_size = data_size * (floCrypto.randInt(MIN_DUMMY_SIZE_MUL * SIZE_FACTOR, MAX_DUMMY_SIZE_MUL * SIZE_FACTOR) / SIZE_FACTOR);
|
||||
f_data = floCrypto.randString(d_size, false);
|
||||
}
|
||||
f_data = Crypto.AES.encrypt(f_data, priv_key);
|
||||
fs.writeFileSync(path.join(_x.index_dir, f_name + INDEX_FILE_EXT), f_data, INDEX_FILE_TYPE);
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
return reject("Index file creation failed");
|
||||
} try { //update prime file
|
||||
let en_filename = Crypto.AES.encrypt(new_filename, priv_key);
|
||||
fs.writeFileSync(_x.prime_file, en_filename, PRIME_FILE_TYPE);
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
return reject("Update prime file failed");
|
||||
}
|
||||
if (cur_filename)
|
||||
fs.unlink(path.join(_x.index_dir, cur_filename + INDEX_FILE_EXT), err => err ? console.debug(err) : null);
|
||||
shuffle.interval = setInterval(shuffle, SHUFFLE_INTERVAL);
|
||||
resolve("Key management initiated");
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function shuffle() {
|
||||
readIndexFile().then(data => {
|
||||
let new_filename, cur_filename = Crypto.AES.decrypt(fs.readFileSync(_x.prime_file, PRIME_FILE_TYPE), _x.node_priv);
|
||||
fs.readdir(_x.index_dir, (err, files) => {
|
||||
if (err)
|
||||
return console.error(err);
|
||||
data = JSON.stringify(data);
|
||||
let data_size = data.length;
|
||||
for (let file of files) {
|
||||
let f_data, f_name = floCrypto.randString(INDEX_FILE_NAME_LENGTH);
|
||||
if (file === cur_filename + INDEX_FILE_EXT) {
|
||||
new_filename = f_name;
|
||||
f_data = data;
|
||||
} else {
|
||||
let d_size = data_size * (floCrypto.randInt(MIN_DUMMY_SIZE_MUL * SIZE_FACTOR, MAX_DUMMY_SIZE_MUL * SIZE_FACTOR) / SIZE_FACTOR);
|
||||
f_data = floCrypto.randString(d_size, false);
|
||||
}
|
||||
f_data = Crypto.AES.encrypt(f_data, _x.node_priv);
|
||||
//rename and rewrite the file
|
||||
try {
|
||||
fs.renameSync(path.join(_x.index_dir, file), path.join(_x.index_dir, f_name + INDEX_FILE_EXT));
|
||||
fs.writeFileSync(path.join(_x.index_dir, f_name + INDEX_FILE_EXT), f_data, INDEX_FILE_TYPE);
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
//update prime file
|
||||
if (!new_filename)
|
||||
return console.error("Index file has not been renamed");
|
||||
let en_filename = Crypto.AES.encrypt(new_filename, _x.node_priv);
|
||||
try {
|
||||
fs.writeFileSync(_x.prime_file, en_filename, PRIME_FILE_TYPE);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
})
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
function readIndexFile() {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(_x.index_file, INDEX_FILE_TYPE, (err, data) => {
|
||||
if (err) {
|
||||
console.debug(err);
|
||||
return reject('Unable to read Index file');
|
||||
}
|
||||
try {
|
||||
data = JSON.parse(Crypto.AES.decrypt(data, _x.node_priv));
|
||||
resolve(data);
|
||||
} catch {
|
||||
reject("Index file corrupted");
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function writeIndexFile(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let en_data = Crypto.AES.encrypt(JSON.stringify(data), _x.node_priv);
|
||||
fs.writeFile(_x.index_file, en_data, INDEX_FILE_TYPE, (err) => {
|
||||
if (err) {
|
||||
console.debug(err);
|
||||
return reject('Unable to write Index file');
|
||||
} else resolve("Updated Index file");
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getShares(group, id, ignoreDiscarded = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
checkIfDiscarded(id).then(result => {
|
||||
if (ignoreDiscarded && result != false)
|
||||
return reject("Trying to get share for discarded ID");
|
||||
readIndexFile().then(data => {
|
||||
if (!(group in data))
|
||||
reject("Group not found in Index file");
|
||||
else if (!(id in data[group]))
|
||||
reject("ID not found in Index file");
|
||||
else {
|
||||
let ref = data[group][id].shift();
|
||||
DB.query("SELECT share FROM sinkShares WHERE num IN (?)", [data[group][id]])
|
||||
.then(result => resolve({ ref, shares: result.map(r => Crypto.AES.decrypt(r.share, _x.node_priv)) }))
|
||||
.catch(error => reject(error))
|
||||
}
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function storeShareAtRandom(share) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let rand = floCrypto.randInt(UNSIGNED_INT_MIN, UNSIGNED_INT_MAX);
|
||||
DB.query("INSERT INTO sinkShares(num, share) VALUE (?)", [[rand, share]])
|
||||
.then(result => resolve(result.insertId)).catch(error => {
|
||||
if (error.code === "ER_DUP_ENTRY")
|
||||
storeShareAtRandom(share) //try again (with diff rand_num)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
else
|
||||
reject(error);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function addShare(group, id, ref, share) {
|
||||
return new Promise((resolve, reject) => {
|
||||
checkIfDiscarded(id).then(result => {
|
||||
if (result != false)
|
||||
return reject("Trying to store share for discarded ID");
|
||||
lockfile.lock(_x.index_file, { retries: { forever: true, minTimeout: LOCK_RETRY_MIN_TIME, maxTimeout: LOCK_RETRY_MAX_TIME } }).then(release => {
|
||||
const releaseAndReject = err => {
|
||||
release().then(_ => null).catch(error => console.error(error));
|
||||
reject(err);
|
||||
}
|
||||
readIndexFile().then(data => {
|
||||
if (!(group in data))
|
||||
data[group] = {};
|
||||
if (!(id in data[group]))
|
||||
data[group][id] = [ref];
|
||||
else if (ref < data[group][id][0])
|
||||
return reject("reference is lower than current");
|
||||
else if (ref > data[group][id][0]) {
|
||||
let old_shares = data[group][id];
|
||||
data[group][id] = [ref];
|
||||
old_shares.shift();
|
||||
DB.query("DELETE FROM sinkShares WHERE num in (?)", [old_shares])//delete old shares
|
||||
.then(_ => null).catch(error => console.error(error));
|
||||
}
|
||||
let encrypted_share = Crypto.AES.encrypt(share, _x.node_priv);
|
||||
console.debug(ref, '|sinkID:', id, '|EnShare:', encrypted_share);
|
||||
storeShareAtRandom(encrypted_share).then(i => {
|
||||
data[group][id].push(i);
|
||||
writeIndexFile(data).then(_ => resolve(i)).catch(error => reject(error))
|
||||
.finally(_ => release().then(_ => null).catch(error => console.error(error)));
|
||||
}).catch(error => releaseAndReject(error))
|
||||
}).catch(error => releaseAndReject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function generateShares(sinkKey, total_n, min_n) {
|
||||
let shares = floCrypto.createShamirsSecretShares(sinkKey, total_n * SHARES_PER_NODE, min_n * SHARES_PER_NODE * SHARE_THRESHOLD);
|
||||
let node_shares = Array(total_n);
|
||||
for (let i = 0; i < total_n; i++)
|
||||
node_shares[i] = shares.splice(0, SHARES_PER_NODE);
|
||||
return node_shares;
|
||||
}
|
||||
|
||||
function getStoredList(group = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
readIndexFile().then(data => {
|
||||
if (group !== null) {
|
||||
if (group in data)
|
||||
resolve(Object.keys(data.group));
|
||||
else
|
||||
reject("Group not found in Index file");
|
||||
} else {
|
||||
let ids = {};
|
||||
for (let group in data)
|
||||
ids[group] = Object.keys(data[group]);
|
||||
resolve(ids);
|
||||
}
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function getDiscardedList() {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT floID, discard_time FROM discardedSinks")
|
||||
.then(result => resolve(Object.fromEntries(result.map(r => [r.floID, r.discard_time]))))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function checkIfDiscarded(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT discard_time FROM discardedSinks WHERE floID=?", [id])
|
||||
.then(result => resolve(result.length ? result[0].discard_time : false))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function discardSink(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("INSERT INTO discardedSinks(floID) VALUE (?)", [id])
|
||||
.then(result => resolve(`Discarded ${id}`))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
//Sink groups and chest
|
||||
const sink_groups = {
|
||||
get EXCHANGE() { return "exchange" },
|
||||
get CONVERT() { return "convert" },
|
||||
get BLOCKCHAIN_BONDS() { return "blockchain_bonds" },
|
||||
get BOBS_FUND() { return "bobs_fund" },
|
||||
get initial_list() { //list to generate when starting exchange
|
||||
return [this.EXCHANGE, this.CONVERT]
|
||||
},
|
||||
get generate_list() { //list allowed to generate
|
||||
return [this.EXCHANGE, this.CONVERT, this.BLOCKCHAIN_BONDS, this.BOBS_FUND]
|
||||
}
|
||||
};
|
||||
|
||||
const sink_ids = {}, sink_chest = {
|
||||
reset() {
|
||||
for (let i in sink_ids)
|
||||
delete sink_ids[i];
|
||||
},
|
||||
set_id(group, id, value) {
|
||||
if (!(group in sink_ids))
|
||||
sink_ids[group] = {};
|
||||
sink_ids[group][id] = value;
|
||||
},
|
||||
rm_id(group, id) {
|
||||
return delete sink_ids[group][id];
|
||||
},
|
||||
get_id(group, id) {
|
||||
return sink_ids[group][id];
|
||||
},
|
||||
list(group) {
|
||||
return Object.keys(sink_ids[group] || {});
|
||||
},
|
||||
active_list(group) {
|
||||
let ids = [];
|
||||
if (group in sink_ids)
|
||||
for (let id in sink_ids[group])
|
||||
if (sink_ids[group][id])
|
||||
ids.push(id);
|
||||
return ids;
|
||||
},
|
||||
includes(group, id) {
|
||||
return group in sink_ids ? (id in sink_ids[group]) : null;
|
||||
},
|
||||
isActive(group, id) {
|
||||
return group in sink_ids ? (id in sink_ids && sink_ids[id]) : null;
|
||||
},
|
||||
pick(group) {
|
||||
let ids = this.list(group),
|
||||
i = floCrypto.randInt(0, ids.length - 1);
|
||||
return ids[i];
|
||||
},
|
||||
active_pick(group) {
|
||||
let ids = this.active_list(group),
|
||||
i = floCrypto.randInt(0, ids.length - 1);
|
||||
return ids[i];
|
||||
},
|
||||
find_group(id) {
|
||||
let group = null;
|
||||
for (let g in sink_ids)
|
||||
if (id in sink_ids[g]) {
|
||||
group = g; break;
|
||||
}
|
||||
return group;
|
||||
},
|
||||
get_all() {
|
||||
let ids = {};
|
||||
for (let g in sink_ids)
|
||||
ids[g] = Object.keys(sink_ids[g])
|
||||
return ids;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
init: initialize,
|
||||
getShares,
|
||||
addShare,
|
||||
generateShares,
|
||||
getStoredList,
|
||||
getDiscardedList,
|
||||
checkIfDiscarded,
|
||||
discardSink,
|
||||
set node_priv(key) {
|
||||
_x.node_priv = key;
|
||||
},
|
||||
get node_priv() {
|
||||
return _x.node_priv;
|
||||
},
|
||||
get node_id() {
|
||||
return node_id;
|
||||
},
|
||||
get node_pub() {
|
||||
return node_pub;
|
||||
},
|
||||
get sink_groups() {
|
||||
return sink_groups;
|
||||
},
|
||||
get sink_chest() {
|
||||
return sink_chest;
|
||||
}
|
||||
}
|
||||
92
src/main.js
92
src/main.js
@ -2,11 +2,19 @@
|
||||
global.floGlobals = require('../docs/scripts/floGlobals');
|
||||
require('./set_globals');
|
||||
require('../docs/scripts/lib');
|
||||
require('../docs/scripts/floCrypto');
|
||||
require('../docs/scripts/floBlockchainAPI');
|
||||
require('../docs/scripts/floTokenAPI');
|
||||
global.floCrypto = require('../docs/scripts/floCrypto');
|
||||
global.floBlockchainAPI = require('../docs/scripts/floBlockchainAPI');
|
||||
global.floTokenAPI = require('../docs/scripts/floTokenAPI');
|
||||
global.btcOperator = require('../docs/scripts/btcOperator');
|
||||
|
||||
const Database = require("./database");
|
||||
(function () {
|
||||
const { adminID, application } = require("../docs/scripts/floExchangeAPI");
|
||||
floGlobals.adminID = adminID;
|
||||
floGlobals.application = application;
|
||||
})();
|
||||
|
||||
const keys = require('./keys');
|
||||
const DB = require("./database");
|
||||
const App = require('./app');
|
||||
|
||||
const backup = require('./backup/head');
|
||||
@ -15,14 +23,15 @@ const {
|
||||
BLOCKCHAIN_REFRESH_INTERVAL
|
||||
} = require("./_constants")["app"];
|
||||
|
||||
var DB, app;
|
||||
var app;
|
||||
|
||||
function refreshData(startup = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
refreshDataFromBlockchain().then(result => {
|
||||
loadDataFromDB(result, startup)
|
||||
.then(_ => resolve("Data refresh successful"))
|
||||
.catch(error => reject(error))
|
||||
loadDataFromDB(result, startup).then(_ => {
|
||||
app.refreshData(backup.nodeList);
|
||||
resolve("Data refresh successful")
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
@ -50,13 +59,16 @@ function refreshDataFromBlockchain() {
|
||||
promises.push(DB.query("DELETE FROM NodeList WHERE floID=?", [n]));
|
||||
if (content.Nodes.add)
|
||||
for (let n in content.Nodes.add)
|
||||
promises.push(DB.query("INSERT INTO NodeList (floID, uri) VALUE (?,?) ON DUPLICATE KEY UPDATE uri=?", [n, content.Nodes.add[n], content.Nodes.add[n]]));
|
||||
promises.push(DB.query("INSERT INTO NodeList (floID, uri) VALUE (?) ON DUPLICATE KEY UPDATE uri=?", [[n, content.Nodes.add[n]], content.Nodes.add[n]]));
|
||||
if (content.Nodes.update)
|
||||
for (let n in content.Nodes.update)
|
||||
promises.push(DB.query("UPDATE NodeList SET uri=? WHERE floID=?", [content.Nodes.update[n], n]));
|
||||
}
|
||||
//Asset List
|
||||
if (content.Assets) {
|
||||
assets_change = true;
|
||||
for (let a in content.Assets)
|
||||
promises.push(DB.query("INSERT INTO AssetList (asset, initialPrice) VALUE (?,?) ON DUPLICATE KEY UPDATE initialPrice=?", [a, content.Assets[a], content.Assets[a]]));
|
||||
promises.push(DB.query("INSERT INTO AssetList (asset, initialPrice) VALUE (?) ON DUPLICATE KEY UPDATE initialPrice=?", [[a, content.Assets[a]], content.Assets[a]]));
|
||||
}
|
||||
//Trusted List
|
||||
if (content.Trusted) {
|
||||
@ -68,26 +80,26 @@ function refreshDataFromBlockchain() {
|
||||
for (let id of content.Trusted.add)
|
||||
promises.push(DB.query("INSERT INTO TrustedList (floID) VALUE (?) ON DUPLICATE KEY UPDATE floID=floID", [id]));
|
||||
}
|
||||
//Tag List with priority and API
|
||||
//Tag List with priority
|
||||
if (content.Tag) {
|
||||
if (content.Tag.remove)
|
||||
for (let t of content.Tag.remove)
|
||||
promises.push(DB.query("DELETE FROM TagList WHERE tag=?", [t]));
|
||||
if (content.Tag.add)
|
||||
for (let t in content.Tag.add)
|
||||
promises.push(DB.query("INSERT INTO TagList (tag, sellPriority, buyPriority, api) VALUE (?,?,?,?) ON DUPLICATE KEY UPDATE tag=tag", [t, content.Tag.add[t].sellPriority, content.Tag.add[t].buyPriority, content.Tag.add[t].api]));
|
||||
promises.push(DB.query("INSERT INTO TagList (tag, sellPriority, buyPriority) VALUE (?) ON DUPLICATE KEY UPDATE tag=tag", [[t, content.Tag.add[t].sellPriority, content.Tag.add[t].buyPriority]]));
|
||||
if (content.Tag.update)
|
||||
for (let t in content.Tag.update)
|
||||
for (let a in content.Tag.update[t])
|
||||
promises.push(`UPDATE TagList WHERE tag=? SET ${a}=?`, [t, content.Tag.update[t][a]]);
|
||||
}
|
||||
});
|
||||
promises.push(DB.query("INSERT INTO LastTx (floID, num) VALUE (?, ?) ON DUPLICATE KEY UPDATE num=?", [floGlobals.adminID, result.totalTxs, result.totalTxs]));
|
||||
promises.push(DB.query("INSERT INTO LastTx (floID, num) VALUE (?) ON DUPLICATE KEY UPDATE num=?", [[floGlobals.adminID, result.totalTxs], result.totalTxs]));
|
||||
//Check if all save process were successful
|
||||
Promise.allSettled(promises).then(results => {
|
||||
//console.debug(results.filter(r => r.status === "rejected"));
|
||||
if (results.reduce((a, r) => r.status === "rejected" ? ++a : a, 0))
|
||||
console.warn("Some data might not have been saved in database correctly");
|
||||
console.warn("Some blockchain data might not have been saved in database correctly");
|
||||
resolve({
|
||||
nodes: nodes_change,
|
||||
assets: assets_change,
|
||||
@ -114,7 +126,7 @@ function loadDataFromDB(changes, startup) {
|
||||
})
|
||||
}
|
||||
|
||||
loadDataFromDB.nodeList = function() {
|
||||
loadDataFromDB.nodeList = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT floID, uri FROM NodeList").then(result => {
|
||||
let nodes = {}
|
||||
@ -127,7 +139,7 @@ loadDataFromDB.nodeList = function() {
|
||||
})
|
||||
}
|
||||
|
||||
loadDataFromDB.assetList = function() {
|
||||
loadDataFromDB.assetList = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT asset FROM AssetList").then(result => {
|
||||
let assets = [];
|
||||
@ -141,7 +153,7 @@ loadDataFromDB.assetList = function() {
|
||||
})
|
||||
}
|
||||
|
||||
loadDataFromDB.trustedIDs = function() {
|
||||
loadDataFromDB.trustedIDs = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT * FROM TrustedList").then(result => {
|
||||
let trustedIDs = [];
|
||||
@ -154,42 +166,40 @@ loadDataFromDB.trustedIDs = function() {
|
||||
})
|
||||
}
|
||||
|
||||
function setDB(db) {
|
||||
DB = db;
|
||||
backup.DB = DB;
|
||||
}
|
||||
|
||||
module.exports = function startServer(public_dir) {
|
||||
const config = require(`../args/config${process.env.I || ""}.json`);
|
||||
module.exports = function startServer() {
|
||||
let _pass, _I = "";
|
||||
for (let arg of process.argv) {
|
||||
if (/^-I=/.test(arg))
|
||||
_I = arg.split(/=(.*)/s)[1];
|
||||
else if (/^-password=/i.test(arg))
|
||||
_pass = arg.split(/=(.*)/s)[1];
|
||||
}
|
||||
const config = require(`../args/config${_I}.json`);
|
||||
try {
|
||||
var _tmp = require(`../args/keys${process.env.I || ""}.json`);
|
||||
let _tmp = require(`../args/keys${_I}.json`);
|
||||
_tmp = floCrypto.retrieveShamirSecret(_tmp);
|
||||
var _pass = process.env.PASSWORD;
|
||||
if (!_pass) {
|
||||
console.error('Password not entered!');
|
||||
process.exit(1);
|
||||
}
|
||||
global.myPrivKey = Crypto.AES.decrypt(_tmp, _pass);
|
||||
global.myPubKey = floCrypto.getPubKeyHex(global.myPrivKey);
|
||||
global.myFloID = floCrypto.getFloID(global.myPubKey);
|
||||
if (!global.myFloID || !global.myPubKey || !global.myPrivKey)
|
||||
throw "Invalid Keys";
|
||||
keys.node_priv = Crypto.AES.decrypt(_tmp, _pass);
|
||||
} catch (error) {
|
||||
console.error('Unable to load private key!');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
global.PUBLIC_DIR = public_dir;
|
||||
console.log("Logged in as", global.myFloID);
|
||||
console.log("Logged in as", keys.node_id);
|
||||
|
||||
Database(config["sql_user"], config["sql_pwd"], config["sql_db"], config["sql_host"]).then(db => {
|
||||
setDB(db);
|
||||
app = new App(config['secret'], DB);
|
||||
refreshData(true).then(_ => {
|
||||
app.start(config['port']).then(result => {
|
||||
console.log(result);
|
||||
backup.init(app);
|
||||
setInterval(refreshData, BLOCKCHAIN_REFRESH_INTERVAL)
|
||||
DB.connect(config["sql_user"], config["sql_pwd"], config["sql_db"], config["sql_host"]).then(result => {
|
||||
keys.init().then(result => {
|
||||
console.log(result);
|
||||
app = new App(config['secret']);
|
||||
refreshData(true).then(_ => {
|
||||
app.start(config['port']).then(result => {
|
||||
console.log(result);
|
||||
backup.init(app);
|
||||
setInterval(refreshData, BLOCKCHAIN_REFRESH_INTERVAL)
|
||||
}).catch(error => console.error(error))
|
||||
}).catch(error => console.error(error))
|
||||
}).catch(error => console.error(error))
|
||||
}).catch(error => console.error(error));
|
||||
|
||||
562
src/market.js
562
src/market.js
@ -1,22 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
const coupling = require('./coupling');
|
||||
const price = require("./price");
|
||||
const background = require('./background');
|
||||
const DB = require("./database");
|
||||
const blockchain = require('./blockchain');
|
||||
|
||||
const {
|
||||
MINIMUM_BUY_REQUIREMENT,
|
||||
TRADE_HASH_PREFIX,
|
||||
TRANSFER_HASH_PREFIX
|
||||
} = require('./_constants')["market"];
|
||||
|
||||
const MINI_PERIOD_INTERVAL = require('./_constants')['app']['PERIOD_INTERVAL'] / 10;
|
||||
const eCode = require('../docs/scripts/floExchangeAPI').errorCode;
|
||||
const pCode = require('../docs/scripts/floExchangeAPI').processCode;
|
||||
|
||||
var DB, assetList; //container for database and allowed assets
|
||||
const updateBalance = background.updateBalance = coupling.updateBalance;
|
||||
|
||||
var assetList; //container for allowed assets
|
||||
|
||||
function login(floID, proxyKey) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("INSERT INTO UserSession (floID, proxyKey) VALUE (?, ?) " +
|
||||
"ON DUPLICATE KEY UPDATE session_time=DEFAULT, proxyKey=?",
|
||||
[floID, proxyKey, proxyKey])
|
||||
DB.query("INSERT INTO UserSession (floID, proxyKey) VALUE (?) ON DUPLICATE KEY UPDATE session_time=DEFAULT, proxyKey=?", [[floID, proxyKey], proxyKey])
|
||||
.then(result => resolve("Login Successful"))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
@ -30,14 +34,25 @@ function logout(floID) {
|
||||
})
|
||||
}
|
||||
|
||||
function getRateHistory(asset, duration) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!asset || !assetList.includes(asset))
|
||||
reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid asset(${asset})`));
|
||||
else
|
||||
price.getHistory(asset, duration)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function getBalance(floID, token) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (floID && !floCrypto.validateAddr(floID))
|
||||
reject(INVALID(`Invalid floID(${floID})`));
|
||||
reject(INVALID(eCode.INVALID_FLO_ID, `Invalid floID(${floID})`));
|
||||
else if (token && token !== floGlobals.currency && !assetList.includes(token))
|
||||
reject(INVALID(`Invalid token(${token})`));
|
||||
reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid token(${token})`));
|
||||
else if (!floID && !token)
|
||||
reject(INVALID('Missing parameters: requires atleast one (floID, token)'));
|
||||
reject(INVALID(eCode.MISSING_PARAMETER, 'Missing parameters: requires atleast one (floID, token)'));
|
||||
else {
|
||||
var promise;
|
||||
if (floID && token)
|
||||
@ -52,55 +67,44 @@ function getBalance(floID, token) {
|
||||
}
|
||||
|
||||
getBalance.floID_token = (floID, token) => new Promise((resolve, reject) => {
|
||||
(token === floGlobals.currency ?
|
||||
DB.query("SELECT IFNULL(SUM(balance), 0) AS balance FROM Cash WHERE floID=?", [floID]) :
|
||||
DB.query("SELECT IFNULL(SUM(quantity), 0) AS balance FROM Vault WHERE floID=? AND asset=?", [floID, token])
|
||||
).then(result => resolve({
|
||||
DB.query("SELECT quantity AS balance FROM UserBalance WHERE floID=? AND token=?", [floID, token]).then(result => resolve({
|
||||
floID,
|
||||
token,
|
||||
balance: result[0].balance.toFixed(4)
|
||||
balance: result.length ? global.toStandardDecimal(result[0].balance) : 0
|
||||
})).catch(error => reject(error))
|
||||
});
|
||||
|
||||
getBalance.floID = (floID) => new Promise((resolve, reject) => {
|
||||
Promise.all([
|
||||
DB.query("SELECT IFNULL(SUM(balance), 0) AS balance FROM Cash WHERE floID=?", [floID]),
|
||||
DB.query("SELECT asset, IFNULL(SUM(quantity), 0) AS balance FROM Vault WHERE floID=? GROUP BY asset", [floID])
|
||||
]).then(result => {
|
||||
DB.query("SELECT token, quantity AS balance FROM UserBalance WHERE floID=?", [floID]).then(result => {
|
||||
let response = {
|
||||
floID,
|
||||
balance: {}
|
||||
};
|
||||
response.balance[floGlobals.currency] = result[0][0].balance.toFixed(4);
|
||||
for (let row of result[1])
|
||||
response.balance[row.asset] = row.balance.toFixed(4);
|
||||
for (let row of result)
|
||||
response.balance[row.token] = global.toStandardDecimal(row.balance);
|
||||
resolve(response);
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
|
||||
getBalance.token = (token) => new Promise((resolve, reject) => {
|
||||
(token === floGlobals.currency ?
|
||||
DB.query("SELECT floID, balance FROM Cash") :
|
||||
DB.query("SELECT floID, IFNULL(SUM(quantity), 0) AS balance FROM Vault WHERE asset = ? GROUP BY floID", [token])
|
||||
).then(result => {
|
||||
DB.query("SELECT floID, quantity AS balance FROM UserBalance WHERE token=?", [token]).then(result => {
|
||||
let response = {
|
||||
token: token,
|
||||
balance: {}
|
||||
};
|
||||
for (let row of result)
|
||||
response.balance[row.floID] = row.balance.toFixed(4);
|
||||
response.balance[row.floID] = global.toStandardDecimal(row.balance);
|
||||
resolve(response);
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
|
||||
const getAssetBalance = (floID, asset) => new Promise((resolve, reject) => {
|
||||
let promises = (asset === floGlobals.currency) ? [
|
||||
DB.query("SELECT IFNULL(SUM(balance), 0) AS balance FROM Cash WHERE floID=?", [floID]),
|
||||
DB.query("SELECT IFNULL(SUM(quantity*maxPrice), 0) AS locked FROM BuyOrder WHERE floID=?", [floID])
|
||||
] : [
|
||||
DB.query("SELECT IFNULL(SUM(quantity), 0) AS balance FROM Vault WHERE floID=? AND asset=?", [floID, asset]),
|
||||
let promises = [];
|
||||
promises.push(DB.query("SELECT IFNULL(SUM(quantity), 0) AS balance FROM UserBalance WHERE floID=? AND token=?", [floID, asset]));
|
||||
promises.push(asset === floGlobals.currency ?
|
||||
DB.query("SELECT IFNULL(SUM(quantity*maxPrice), 0) AS locked FROM BuyOrder WHERE floID=?", [floID]) :
|
||||
DB.query("SELECT IFNULL(SUM(quantity), 0) AS locked FROM SellOrder WHERE floID=? AND asset=?", [floID, asset])
|
||||
];
|
||||
);
|
||||
Promise.all(promises).then(result => resolve({
|
||||
total: result[0][0].balance,
|
||||
locked: result[1][0].locked,
|
||||
@ -111,68 +115,59 @@ const getAssetBalance = (floID, asset) => new Promise((resolve, reject) => {
|
||||
getAssetBalance.check = (floID, asset, amount) => new Promise((resolve, reject) => {
|
||||
getAssetBalance(floID, asset).then(balance => {
|
||||
if (balance.total < amount)
|
||||
reject(INVALID(`Insufficient ${asset}`));
|
||||
reject(INVALID(eCode.INSUFFICIENT_BALANCE, `Insufficient ${asset}`));
|
||||
else if (balance.net < amount)
|
||||
reject(INVALID(`Insufficient ${asset} (Some are locked in orders)`));
|
||||
reject(INVALID(eCode.INSUFFICIENT_BALANCE, `Insufficient ${asset} (Some are locked in orders)`));
|
||||
else
|
||||
resolve(true);
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
|
||||
const consumeAsset = (floID, asset, amount, txQueries = []) => new Promise((resolve, reject) => {
|
||||
//If asset/token is currency update Cash else consume from Vault
|
||||
if (asset === floGlobals.currency) {
|
||||
txQueries.push(["UPDATE Cash SET balance=balance-? WHERE floID=?", [amount, floID]]);
|
||||
return resolve(txQueries);
|
||||
} else
|
||||
DB.query("SELECT id, quantity FROM Vault WHERE floID=? AND asset=? ORDER BY locktime", [floID, asset]).then(coins => {
|
||||
let rem = amount;
|
||||
for (let i = 0; i < coins.length && rem > 0; i++) {
|
||||
if (rem < coins[i].quantity) {
|
||||
txQueries.push(["UPDATE Vault SET quantity=quantity-? WHERE id=?", [rem, coins[i].id]]);
|
||||
rem = 0;
|
||||
} else {
|
||||
txQueries.push(["DELETE FROM Vault WHERE id=?", [coins[i].id]]);
|
||||
rem -= coins[i].quantity;
|
||||
}
|
||||
}
|
||||
if (rem > 0) //should not happen AS the total and net is checked already
|
||||
reject(INVALID("Insufficient Asset"));
|
||||
else
|
||||
resolve(txQueries);
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
|
||||
function addSellOrder(floID, asset, quantity, min_price) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floCrypto.validateAddr(floID))
|
||||
return reject(INVALID(`Invalid floID (${floID})`));
|
||||
return reject(INVALID(eCode.INVALID_FLO_ID, `Invalid floID (${floID})`));
|
||||
else if (typeof quantity !== "number" || quantity <= 0)
|
||||
return reject(INVALID(`Invalid quantity (${quantity})`));
|
||||
return reject(INVALID(eCode.INVALID_NUMBER, `Invalid quantity (${quantity})`));
|
||||
else if (typeof min_price !== "number" || min_price <= 0)
|
||||
return reject(INVALID(`Invalid min_price (${min_price})`));
|
||||
return reject(INVALID(eCode.INVALID_NUMBER, `Invalid min_price (${min_price})`));
|
||||
else if (!assetList.includes(asset))
|
||||
return reject(INVALID(`Invalid asset (${asset})`));
|
||||
return reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid asset (${asset})`));
|
||||
getAssetBalance.check(floID, asset, quantity).then(_ => {
|
||||
checkSellRequirement(floID, asset).then(_ => {
|
||||
DB.query("INSERT INTO SellOrder(floID, asset, quantity, minPrice) VALUES (?, ?, ?, ?)", [floID, asset, quantity, min_price])
|
||||
.then(result => resolve('Sell Order placed successfully'))
|
||||
.catch(error => reject(error));
|
||||
checkSellRequirement(floID, asset, quantity, min_price).then(_ => {
|
||||
DB.query("INSERT INTO SellOrder(floID, asset, quantity, minPrice) VALUES (?)", [[floID, asset, quantity, min_price]]).then(result => {
|
||||
resolve('Sell Order placed successfully');
|
||||
coupling.initiate(asset);
|
||||
}).catch(error => reject(error));
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
const checkSellRequirement = (floID, asset) => new Promise((resolve, reject) => {
|
||||
DB.query("SELECT * FROM UserTag WHERE floID=? AND tag=?", [floID, "MINER"]).then(result => {
|
||||
if (result.length)
|
||||
return resolve(true);
|
||||
//TODO: Should seller need to buy same type of asset before selling?
|
||||
DB.query("SELECT IFNULL(SUM(quantity), 0) AS brought FROM TradeTransactions WHERE buyer=? AND asset=?", [floID, asset]).then(result => {
|
||||
if (result[0].brought >= MINIMUM_BUY_REQUIREMENT)
|
||||
resolve(true);
|
||||
const checkSellRequirement = (floID, asset, quantity, min_price) => new Promise((resolve, reject) => {
|
||||
Promise.all([
|
||||
DB.query("SELECT IFNULL(SUM(quantity), 0) AS total_chips FROM SellChips WHERE floID=? AND asset=?", [floID, asset]),
|
||||
DB.query("SELECT IFNULL(SUM(quantity), 0) AS locked FROM SellOrder WHERE floID=? AND asset=?", [floID, asset])
|
||||
]).then(result => {
|
||||
let total = result[0][0].total_chips,
|
||||
locked = result[1][0].locked;
|
||||
if (total < locked + quantity)
|
||||
reject(INVALID(eCode.INSUFFICIENT_SELLCHIP, `Insufficient sell-chips for ${asset}`));
|
||||
else Promise.all([
|
||||
DB.query("SELECT IFNULL(SUM(quantity), 0) AS total_chips FROM SellChips WHERE floID=? AND asset=? AND base<=?", [floID, asset, min_price]),
|
||||
DB.query("SELECT IFNULL(SUM(quantity), 0) AS locked FROM SellOrder WHERE floID=? AND asset=? AND minPrice<=?", [floID, asset, min_price])
|
||||
]).then(result => {
|
||||
let g_total = result[0][0].total_chips,
|
||||
g_locked = result[1][0].locked;
|
||||
let l_total = total - g_total,
|
||||
l_locked = locked - g_locked;
|
||||
var rem = g_total - g_locked;
|
||||
if (l_locked > l_total)
|
||||
rem -= l_locked - l_total;
|
||||
if (rem < quantity)
|
||||
reject(INVALID(eCode.GREATER_SELLCHIP_BASE, `Cannot sell below purchased price`));
|
||||
else
|
||||
reject(INVALID(`Sellers required to buy atleast ${MINIMUM_BUY_REQUIREMENT} ${asset} before placing a sell order unless they are a Miner`));
|
||||
resolve(true);
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
@ -180,17 +175,18 @@ const checkSellRequirement = (floID, asset) => new Promise((resolve, reject) =>
|
||||
function addBuyOrder(floID, asset, quantity, max_price) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floCrypto.validateAddr(floID))
|
||||
return reject(INVALID(`Invalid floID (${floID})`));
|
||||
return reject(INVALID(eCode.INVALID_FLO_ID, `Invalid floID (${floID})`));
|
||||
else if (typeof quantity !== "number" || quantity <= 0)
|
||||
return reject(INVALID(`Invalid quantity (${quantity})`));
|
||||
return reject(INVALID(eCode.INVALID_NUMBER, `Invalid quantity (${quantity})`));
|
||||
else if (typeof max_price !== "number" || max_price <= 0)
|
||||
return reject(INVALID(`Invalid max_price (${max_price})`));
|
||||
return reject(INVALID(eCode.INVALID_NUMBER, `Invalid max_price (${max_price})`));
|
||||
else if (!assetList.includes(asset))
|
||||
return reject(INVALID(`Invalid asset (${asset})`));
|
||||
return reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid asset (${asset})`));
|
||||
getAssetBalance.check(floID, floGlobals.currency, quantity * max_price).then(_ => {
|
||||
DB.query("INSERT INTO BuyOrder(floID, asset, quantity, maxPrice) VALUES (?, ?, ?, ?)", [floID, asset, quantity, max_price])
|
||||
.then(result => resolve('Buy Order placed successfully'))
|
||||
.catch(error => reject(error));
|
||||
DB.query("INSERT INTO BuyOrder(floID, asset, quantity, maxPrice) VALUES (?)", [[floID, asset, quantity, max_price]]).then(result => {
|
||||
resolve('Buy Order placed successfully');
|
||||
coupling.initiate(asset);
|
||||
}).catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
@ -198,23 +194,25 @@ function addBuyOrder(floID, asset, quantity, max_price) {
|
||||
function cancelOrder(type, id, floID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floCrypto.validateAddr(floID))
|
||||
return reject(INVALID(`Invalid floID (${floID})`));
|
||||
return reject(INVALID(eCode.INVALID_FLO_ID, `Invalid floID (${floID})`));
|
||||
let tableName;
|
||||
if (type === "buy")
|
||||
tableName = "BuyOrder";
|
||||
else if (type === "sell")
|
||||
tableName = "SellOrder";
|
||||
else
|
||||
return reject(INVALID("Invalid Order type! Order type must be buy (or) sell"));
|
||||
DB.query(`SELECT floID FROM ${tableName} WHERE id=?`, [id]).then(result => {
|
||||
return reject(INVALID(eCode.INVALID_TYPE, "Invalid Order type! Order type must be buy (or) sell"));
|
||||
DB.query(`SELECT floID, asset FROM ${tableName} WHERE id=?`, [id]).then(result => {
|
||||
if (result.length < 1)
|
||||
return reject(INVALID("Order not found!"));
|
||||
return reject(INVALID(eCode.NOT_FOUND, "Order not found!"));
|
||||
else if (result[0].floID !== floID)
|
||||
return reject(INVALID("Order doesnt belong to the current user"));
|
||||
return reject(INVALID(eCode.NOT_OWNER, "Order doesnt belong to the current user"));
|
||||
let asset = result[0].asset;
|
||||
//Delete the order
|
||||
DB.query(`DELETE FROM ${tableName} WHERE id=?`, [id])
|
||||
.then(result => resolve(tableName + "#" + id + " cancelled successfully"))
|
||||
.catch(error => reject(error));
|
||||
DB.query(`DELETE FROM ${tableName} WHERE id=?`, [id]).then(result => {
|
||||
resolve(tableName + "#" + id + " cancelled successfully");
|
||||
coupling.initiate(asset);
|
||||
}).catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
@ -222,8 +220,7 @@ function cancelOrder(type, id, floID) {
|
||||
function getAccountDetails(floID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let select = [];
|
||||
select.push(["balance", "Cash"]);
|
||||
select.push(["asset, AVG(base) AS avg_base, IFNULL(SUM(quantity), 0) AS quantity", "Vault", "GROUP BY asset"]);
|
||||
select.push(["token, quantity", "UserBalance"]);
|
||||
select.push(["id, asset, quantity, minPrice, time_placed", "SellOrder"]);
|
||||
select.push(["id, asset, quantity, maxPrice, time_placed", "BuyOrder"]);
|
||||
let promises = select.map(a => DB.query(`SELECT ${a[0]} FROM ${a[1]} WHERE floID=? ${a[2] || ""}`, [floID]));
|
||||
@ -238,15 +235,12 @@ function getAccountDetails(floID) {
|
||||
else
|
||||
switch (i) {
|
||||
case 0:
|
||||
response.cash = a.value.length ? a.value[0].balance : 0;
|
||||
response.tokenBalance = a.value;
|
||||
break;
|
||||
case 1:
|
||||
response.vault = a.value;
|
||||
break;
|
||||
case 2:
|
||||
response.sellOrders = a.value;
|
||||
break;
|
||||
case 3:
|
||||
case 2:
|
||||
response.buyOrders = a.value;
|
||||
break;
|
||||
}
|
||||
@ -259,6 +253,14 @@ function getAccountDetails(floID) {
|
||||
});
|
||||
}
|
||||
|
||||
function getUserTransacts(floID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT mode, asset, amount, txid, locktime, r_status FROM VaultTransactions WHERE floID=?", [floID])
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function getTransactionDetails(txid) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let tableName, type;
|
||||
@ -269,7 +271,7 @@ function getTransactionDetails(txid) {
|
||||
tableName = 'TradeTransactions';
|
||||
type = 'trade';
|
||||
} else
|
||||
return reject(INVALID("Invalid TransactionID"));
|
||||
return reject(INVALID(eCode.INVALID_TX_ID, "Invalid TransactionID"));
|
||||
DB.query(`SELECT * FROM ${tableName} WHERE txid=?`, [txid]).then(result => {
|
||||
if (result.length) {
|
||||
let details = result[0];
|
||||
@ -278,7 +280,7 @@ function getTransactionDetails(txid) {
|
||||
details.receiver = JSON.parse(details.receiver);
|
||||
resolve(details);
|
||||
} else
|
||||
reject(INVALID("Transaction not found"));
|
||||
reject(INVALID(eCode.NOT_FOUND, "Transaction not found"));
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
@ -286,9 +288,9 @@ function getTransactionDetails(txid) {
|
||||
function transferToken(sender, receivers, token) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floCrypto.validateAddr(sender))
|
||||
reject(INVALID(`Invalid sender (${sender})`));
|
||||
reject(INVALID(eCode.INVALID_FLO_ID, `Invalid sender (${sender})`));
|
||||
else if (token !== floGlobals.currency && !assetList.includes(token))
|
||||
reject(INVALID(`Invalid token (${token})`));
|
||||
reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid token (${token})`));
|
||||
else {
|
||||
let invalidIDs = [],
|
||||
totalAmount = 0;
|
||||
@ -298,15 +300,16 @@ function transferToken(sender, receivers, token) {
|
||||
else
|
||||
totalAmount += receivers[floID];
|
||||
if (invalidIDs.length)
|
||||
reject(INVALID(`Invalid receiver (${invalidIDs})`));
|
||||
reject(INVALID(eCode.INVALID_FLO_ID, `Invalid receiver (${invalidIDs})`));
|
||||
else getAssetBalance.check(sender, token, totalAmount).then(_ => {
|
||||
consumeAsset(sender, token, totalAmount).then(txQueries => {
|
||||
if (token === floGlobals.currency)
|
||||
let txQueries = [];
|
||||
txQueries.push(updateBalance.consume(sender, token, totalAmount));
|
||||
for (let floID in receivers)
|
||||
txQueries.push(updateBalance.add(floID, token, receivers[floID]));
|
||||
checkDistributor(sender, token).then(result => {
|
||||
if (result)
|
||||
for (let floID in receivers)
|
||||
txQueries.push(["INSERT INTO Cash (floID, balance) VALUE (?, ?) ON DUPLICATE KEY UPDATE balance=balance+?", [floID, receivers[floID], receivers[floID]]]);
|
||||
else
|
||||
for (let floID in receivers)
|
||||
txQueries.push(["INSERT INTO Vault(floID, quantity, asset) VALUES (?, ?, ?)", [floID, receivers[floID], token]]);
|
||||
txQueries.push(["INSERT INTO SellChips (floID, asset, quantity) VALUES (?)", [[floID, token, receivers[floID]]]]);
|
||||
let time = Date.now();
|
||||
let hash = TRANSFER_HASH_PREFIX + Crypto.SHA256(JSON.stringify({
|
||||
sender: sender,
|
||||
@ -316,8 +319,8 @@ function transferToken(sender, receivers, token) {
|
||||
tx_time: time,
|
||||
}));
|
||||
txQueries.push([
|
||||
"INSERT INTO TransferTransactions (sender, receiver, token, totalAmount, tx_time, txid) VALUE (?, ?, ?, ?, ?, ?)",
|
||||
[sender, JSON.stringify(receivers), token, totalAmount, global.convertDateToString(time), hash]
|
||||
"INSERT INTO TransferTransactions (sender, receiver, token, totalAmount, tx_time, txid) VALUE (?)",
|
||||
[[sender, JSON.stringify(receivers), token, totalAmount, new Date(time), hash]]
|
||||
]);
|
||||
DB.transaction(txQueries)
|
||||
.then(result => resolve(hash))
|
||||
@ -330,312 +333,171 @@ function transferToken(sender, receivers, token) {
|
||||
|
||||
function depositFLO(floID, txid) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT status FROM InputFLO WHERE txid=? AND floID=?", [txid, floID]).then(result => {
|
||||
DB.query("SELECT r_status FROM VaultTransactions WHERE txid=? AND floID=? AND asset=?", [txid, floID, "FLO"]).then(result => {
|
||||
if (result.length) {
|
||||
switch (result[0].status) {
|
||||
case "PENDING":
|
||||
return reject(INVALID("Transaction already in process"));
|
||||
case "REJECTED":
|
||||
return reject(INVALID("Transaction already rejected"));
|
||||
case "SUCCESS":
|
||||
return reject(INVALID("Transaction already used to add coins"));
|
||||
switch (result[0].r_status) {
|
||||
case pCode.STATUS_PENDING:
|
||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already in process"));
|
||||
case pCode.STATUS_REJECTED:
|
||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already rejected"));
|
||||
case pCode.STATUS_SUCCESS:
|
||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already used to add coins"));
|
||||
}
|
||||
} else
|
||||
DB.query("INSERT INTO InputFLO(txid, floID, status) VALUES (?, ?, ?)", [txid, floID, "PENDING"])
|
||||
.then(result => resolve("Deposit request in process"))
|
||||
.catch(error => reject(error));
|
||||
DB.query("INSERT INTO VaultTransactions(floID, mode, asset_type, asset, txid, r_status) VALUES (?)", [[floID, pCode.VAULT_MODE_DEPOSIT, pCode.ASSET_TYPE_COIN, "FLO", txid, pCode.STATUS_PENDING]])
|
||||
.then(result => resolve("Deposit request in process"))
|
||||
.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 => {
|
||||
let txQueries = [];
|
||||
txQueries.push(["INSERT INTO Vault(floID, quantity, asset) VALUES (?, ?, ?)", [req.floID, amount, "FLO"]]);
|
||||
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);
|
||||
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) => {
|
||||
let receiver = global.sinkID; //receiver should be market's floID (ie, sinkID)
|
||||
if (!receiver)
|
||||
return reject([false, 'sinkID not loaded']);
|
||||
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) => v.scriptPubKey.addresses[0] === receiver ? a + v.value : a, 0);
|
||||
if (amount == 0)
|
||||
return reject([true, "Transaction receiver is not market ID"]);
|
||||
else
|
||||
resolve(amount);
|
||||
}).catch(error => reject([false, error]))
|
||||
})
|
||||
}
|
||||
|
||||
function withdrawFLO(floID, amount) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floCrypto.validateAddr(floID))
|
||||
return reject(INVALID(`Invalid floID (${floID})`));
|
||||
return reject(INVALID(eCode.INVALID_FLO_ID, `Invalid floID (${floID})`));
|
||||
else if (typeof amount !== "number" || amount <= 0)
|
||||
return reject(INVALID(`Invalid amount (${amount})`));
|
||||
return reject(INVALID(eCode.INVALID_NUMBER, `Invalid amount (${amount})`));
|
||||
getAssetBalance.check(floID, "FLO", amount).then(_ => {
|
||||
consumeAsset(floID, "FLO", amount).then(txQueries => {
|
||||
DB.transaction(txQueries).then(result => {
|
||||
//Send FLO to user via blockchain API
|
||||
floBlockchainAPI.sendTx(global.sinkID, floID, amount, global.sinkPrivKey, 'Withdraw FLO Coins from Market').then(txid => {
|
||||
if (!txid)
|
||||
throw Error("Transaction not successful");
|
||||
//Transaction was successful, Add in DB
|
||||
DB.query("INSERT INTO OutputFLO (floID, amount, txid, status) VALUES (?, ?, ?, ?)", [floID, amount, txid, "WAITING_CONFIRMATION"])
|
||||
.then(_ => null).catch(error => console.error(error))
|
||||
.finally(_ => resolve("Withdrawal was successful"));
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
DB.query("INSERT INTO OutputFLO (floID, amount, status) VALUES (?, ?, ?)", [floID, amount, "PENDING"])
|
||||
.then(_ => null).catch(error => console.error(error))
|
||||
.finally(_ => resolve("Withdrawal request is in process"));
|
||||
});
|
||||
}).catch(error => reject(error));
|
||||
let txQueries = [];
|
||||
txQueries.push(updateBalance.consume(floID, "FLO", amount));
|
||||
DB.transaction(txQueries).then(result => {
|
||||
blockchain.withdrawAsset.init(floID, "FLO", amount);
|
||||
resolve("Withdrawal request is in process");
|
||||
}).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 => {
|
||||
floBlockchainAPI.sendTx(global.sinkID, req.floID, req.amount, global.sinkPrivKey, 'Withdraw FLO Coins from Market').then(txid => {
|
||||
if (!txid)
|
||||
throw Error("Transaction not successful");
|
||||
//Transaction was successful, Add in DB
|
||||
DB.query("UPDATE OutputFLO SET status=?, txid=? WHERE id=?", ["WAITING_CONFIRMATION", txid, req.id])
|
||||
.then(_ => null).catch(error => console.error(error));
|
||||
}).catch(error => console.error(error));
|
||||
})
|
||||
}).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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT status FROM InputToken WHERE txid=? AND floID=?", [txid, floID]).then(result => {
|
||||
DB.query("SELECT r_status FROM VaultTransactions WHERE txid=? AND floID=? AND asset_type=?", [txid, floID, pCode.ASSET_TYPE_TOKEN]).then(result => {
|
||||
if (result.length) {
|
||||
switch (result[0].status) {
|
||||
case "PENDING":
|
||||
return reject(INVALID("Transaction already in process"));
|
||||
case "REJECTED":
|
||||
return reject(INVALID("Transaction already rejected"));
|
||||
case "SUCCESS":
|
||||
return reject(INVALID("Transaction already used to add tokens"));
|
||||
switch (result[0].r_status) {
|
||||
case pCode.STATUS_PENDING:
|
||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already in process"));
|
||||
case pCode.STATUS_REJECTED:
|
||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already rejected"));
|
||||
case pCode.STATUS_SUCCESS:
|
||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already used to add tokens"));
|
||||
}
|
||||
} else
|
||||
DB.query("INSERT INTO InputToken(txid, floID, status) VALUES (?, ?, ?)", [txid, floID, "PENDING"])
|
||||
.then(result => resolve("Deposit request in process"))
|
||||
.catch(error => reject(error));
|
||||
DB.query("INSERT INTO VaultTransactions(floID, mode, asset_type, txid, r_status) VALUES (?)", [[floID, pCode.VAULT_MODE_DEPOSIT, pCode.ASSET_TYPE_TOKEN, txid, pCode.STATUS_PENDING]])
|
||||
.then(result => resolve("Deposit request in process"))
|
||||
.catch(error => reject(error));
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
|
||||
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(["INSERT INTO Vault(floID, asset, quantity) VALUES (?, ?, ?)", [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]]);
|
||||
if (token_name === floGlobals.currency)
|
||||
txQueries.push(["INSERT INTO Cash (floID, balance) VALUE (?, ?) ON DUPLICATE KEY UPDATE balance=balance+?", [req.floID, amount_token, amount_token]]);
|
||||
else
|
||||
txQueries.push(["INSERT INTO Vault(floID, asset, quantity) VALUES (?, ?, ?)", [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) => {
|
||||
let receiver = global.sinkID; //receiver should be market's floID (ie, sinkID)
|
||||
if (!receiver)
|
||||
return reject([false, 'sinkID not loaded']);
|
||||
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) => v.scriptPubKey.addresses[0] === receiver ? a + v.value : a, 0);
|
||||
if (amount_flo == 0)
|
||||
return reject([true, "Transaction receiver is not market ID"]);
|
||||
else
|
||||
resolve([token_name, amount_token, amount_flo]);
|
||||
}).catch(error => reject([false, error]))
|
||||
})
|
||||
}
|
||||
|
||||
function withdrawToken(floID, token, amount) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floCrypto.validateAddr(floID))
|
||||
return reject(INVALID(`Invalid floID (${floID})`));
|
||||
return reject(INVALID(eCode.INVALID_FLO_ID, `Invalid floID (${floID})`));
|
||||
else if (typeof amount !== "number" || amount <= 0)
|
||||
return reject(INVALID(`Invalid amount (${amount})`));
|
||||
return reject(INVALID(eCode.INVALID_NUMBER, `Invalid amount (${amount})`));
|
||||
else if ((!assetList.includes(token) && token !== floGlobals.currency) || token === "FLO")
|
||||
return reject(INVALID("Invalid Token"));
|
||||
return reject(INVALID(eCode.INVALID_TOKEN_NAME, "Invalid Token"));
|
||||
//Check for FLO balance (transaction fee)
|
||||
let required_flo = floGlobals.sendAmt + floGlobals.fee;
|
||||
getAssetBalance.check(floID, "FLO", required_flo).then(_ => {
|
||||
getAssetBalance.check(floID, token, amount).then(_ => {
|
||||
consumeAsset(floID, "FLO", required_flo).then(txQueries => {
|
||||
consumeAsset(floID, token, amount, txQueries).then(txQueries => {
|
||||
DB.transaction(txQueries).then(result => {
|
||||
//Send FLO to user via blockchain API
|
||||
floTokenAPI.sendToken(global.sinkPrivKey, amount, floID, '(withdrawal from market)', token).then(txid => {
|
||||
if (!txid) throw Error("Transaction not successful");
|
||||
//Transaction was successful, Add in DB
|
||||
DB.query("INSERT INTO OutputToken (floID, token, amount, txid, status) VALUES (?, ?, ?, ?, ?)", [floID, token, amount, txid, "WAITING_CONFIRMATION"])
|
||||
.then(_ => null).catch(error => console.error(error))
|
||||
.finally(_ => resolve("Withdrawal was successful"));
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
DB.query("INSERT INTO OutputToken (floID, token, amount, status) VALUES (?, ?, ?, ?)", [floID, token, amount, "PENDING"])
|
||||
.then(_ => null).catch(error => console.error(error))
|
||||
.finally(_ => resolve("Withdrawal request is in process"));
|
||||
});
|
||||
}).catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
let txQueries = [];
|
||||
txQueries.push(updateBalance.consume(floID, "FLO", required_flo));
|
||||
txQueries.push(updateBalance.consume(floID, token, amount));
|
||||
DB.transaction(txQueries).then(result => {
|
||||
//Send Token to user via token API
|
||||
blockchain.withdrawAsset.init(floID, token, amount);
|
||||
resolve("Withdrawal request is in process");
|
||||
}).catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
function retryWithdrawalToken() {
|
||||
DB.query("SELECT id, floID, token, amount FROM OutputToken WHERE status=?", ["PENDING"]).then(results => {
|
||||
results.forEach(req => {
|
||||
floTokenAPI.sendToken(global.sinkPrivKey, req.amount, req.floID, '(withdrawal from market)', req.token).then(txid => {
|
||||
if (!txid)
|
||||
throw Error("Transaction not successful");
|
||||
//Transaction was successful, Add in DB
|
||||
DB.query("UPDATE OutputToken SET status=?, txid=? WHERE id=?", ["WAITING_CONFIRMATION", txid, req.id])
|
||||
.then(_ => null).catch(error => console.error(error));
|
||||
}).catch(error => console.error(error));
|
||||
});
|
||||
}).catch(error => reject(error));
|
||||
function addTag(floID, tag) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("INSERT INTO UserTag (floID, tag) VALUE (?)", [[floID, tag]])
|
||||
.then(result => resolve(`Added ${floID} to ${tag}`))
|
||||
.catch(error => {
|
||||
if (error.code === "ER_DUP_ENTRY")
|
||||
reject(INVALID(eCode.DUPLICATE_ENTRY, `${floID} already in ${tag}`));
|
||||
else if (error.code === "ER_NO_REFERENCED_ROW")
|
||||
reject(INVALID(eCode.INVALID_TAG, `Invalid Tag`));
|
||||
else
|
||||
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 removeTag(floID, tag) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("DELETE FROM UserTag WHERE floID=? AND tag=?", [floID, tag])
|
||||
.then(result => resolve(`Removed ${floID} from ${tag}`))
|
||||
.catch(error => reject(error));
|
||||
})
|
||||
}
|
||||
|
||||
function periodicProcess() {
|
||||
blockchainReCheck();
|
||||
assetList.forEach(asset => coupling.initiate(asset));
|
||||
function addDistributor(floID, asset) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("INSERT INTO Distributors (floID, asset) VALUE (?)", [[floID, asset]])
|
||||
.then(result => resolve(`Added ${asset} distributor: ${floID}`))
|
||||
.catch(error => {
|
||||
if (error.code === "ER_DUP_ENTRY")
|
||||
reject(INVALID(eCode.DUPLICATE_ENTRY, `${floID} is already ${asset} disributor`));
|
||||
else if (error.code === "ER_NO_REFERENCED_ROW")
|
||||
reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid Asset`));
|
||||
else
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function blockchainReCheck() {
|
||||
if (blockchainReCheck.timeout) {
|
||||
clearTimeout(blockchainReCheck.timeout);
|
||||
blockchainReCheck.timeout = null;
|
||||
}
|
||||
if (!global.sinkID)
|
||||
return blockchainReCheck.timeout = setTimeout(blockchainReCheck, MINI_PERIOD_INTERVAL);
|
||||
|
||||
confirmDepositFLO();
|
||||
confirmDepositToken();
|
||||
retryWithdrawalFLO();
|
||||
retryWithdrawalToken();
|
||||
confirmWithdrawalFLO();
|
||||
confirmWithdrawalToken();
|
||||
function removeDistributor(floID, asset) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("DELETE FROM Distributors WHERE floID=? AND tag=?", [floID, asset])
|
||||
.then(result => resolve(`Removed ${asset} distributor: ${floID}`))
|
||||
.catch(error => reject(error));
|
||||
})
|
||||
}
|
||||
|
||||
function checkDistributor(floID, asset) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT id FROM Distributors WHERE floID=? AND asset=?", [floID, asset])
|
||||
.then(result => resolve(result.length ? true : false))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
blockchainReCheck.timeout = null;
|
||||
|
||||
module.exports = {
|
||||
login,
|
||||
logout,
|
||||
get rates() {
|
||||
return coupling.price.currentRates;
|
||||
return price.currentRates;
|
||||
},
|
||||
get priceCountDown() {
|
||||
return price.lastTimes;
|
||||
},
|
||||
addBuyOrder,
|
||||
addSellOrder,
|
||||
cancelOrder,
|
||||
getRateHistory,
|
||||
getBalance,
|
||||
getAccountDetails,
|
||||
getUserTransacts,
|
||||
getTransactionDetails,
|
||||
transferToken,
|
||||
depositFLO,
|
||||
withdrawFLO,
|
||||
depositToken,
|
||||
withdrawToken,
|
||||
periodicProcess,
|
||||
group: coupling.group,
|
||||
set DB(db) {
|
||||
DB = db;
|
||||
coupling.DB = db;
|
||||
},
|
||||
addTag,
|
||||
removeTag,
|
||||
addDistributor,
|
||||
removeDistributor,
|
||||
set assetList(assets) {
|
||||
assetList = assets;
|
||||
background.assetList = assets;
|
||||
},
|
||||
get assetList() {
|
||||
return assetList
|
||||
}
|
||||
};
|
||||
151
src/price.js
151
src/price.js
@ -1,4 +1,5 @@
|
||||
'use strict';
|
||||
const DB = require("./database");
|
||||
|
||||
const {
|
||||
MIN_TIME,
|
||||
@ -6,12 +7,11 @@ const {
|
||||
UP_RATE,
|
||||
MAX_DOWN_PER_DAY,
|
||||
MAX_UP_PER_DAY,
|
||||
CHECK_RATED_SELLER,
|
||||
TOP_RANGE,
|
||||
REC_HISTORY_INTERVAL
|
||||
} = require("./_constants")["price"];
|
||||
|
||||
var DB; //container for database
|
||||
|
||||
var currentRate = {}, //container for FLO price (from API or by model)
|
||||
lastTime = {}, //container for timestamp of the last tx
|
||||
noBuyOrder = {},
|
||||
@ -19,15 +19,26 @@ var currentRate = {}, //container for FLO price (from API or by model)
|
||||
|
||||
const updateLastTime = asset => lastTime[asset] = Date.now();
|
||||
|
||||
//store FLO price in DB every 1 hr
|
||||
function storeRate(asset, rate) {
|
||||
DB.query("INSERT INTO PriceHistory (asset, rate) VALUE (?, ?)", [asset, rate.toFixed(3)])
|
||||
//store FLO price in database every 1 hr
|
||||
function storeHistory(asset, rate) {
|
||||
DB.query("INSERT INTO PriceHistory (asset, rate) VALUE (?)", [[asset, global.toStandardDecimal(rate)]])
|
||||
.then(_ => null).catch(error => console.error(error))
|
||||
}
|
||||
setInterval(() => {
|
||||
for (let asset in currentRate)
|
||||
storeRate(asset, currentRate[asset]);
|
||||
}, REC_HISTORY_INTERVAL)
|
||||
|
||||
storeHistory.start = function () {
|
||||
storeHistory.stop();
|
||||
storeHistory.instance = setInterval(() => {
|
||||
for (let asset in currentRate)
|
||||
storeHistory(asset, currentRate[asset]);
|
||||
}, REC_HISTORY_INTERVAL);
|
||||
}
|
||||
|
||||
storeHistory.stop = function () {
|
||||
if (storeHistory.instance !== undefined) {
|
||||
clearInterval(storeHistory.instance);
|
||||
delete storeHistory.instance;
|
||||
}
|
||||
}
|
||||
|
||||
function getPastRate(asset, hrs = 24) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -37,69 +48,69 @@ function getPastRate(asset, hrs = 24) {
|
||||
});
|
||||
}
|
||||
|
||||
function getHistory(asset, duration) {
|
||||
return new Promise((resolve, reject) => {
|
||||
duration = getHistory.validateDuration(duration);
|
||||
let statement = "SELECT " +
|
||||
(!duration || duration.endsWith("month") || duration.endsWith("year") ? "DATE(rec_time) AS time, AVG(rate) as rate" : "rec_time AS time, rate") +
|
||||
" FROM PriceHistory WHERE asset=?" + (duration ? " AND rec_time >= NOW() - INTERVAL " + duration : "") +
|
||||
(!duration || duration.endsWith("month") || duration.endsWith("year") ? " GROUP BY time" : "") +
|
||||
" ORDER BY time";
|
||||
DB.query(statement, asset)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
|
||||
getHistory.validateDuration = duration => {
|
||||
let n = duration.match(/\d+/g),
|
||||
d = duration.match(/\D+/g);
|
||||
n = n ? n[0] || 1 : 1;
|
||||
d = d ? d[0].replace(/[-\s]/g, '') : "";
|
||||
switch (d.toLowerCase()) {
|
||||
case "day":
|
||||
case "days":
|
||||
return n + " day";
|
||||
case "week":
|
||||
case "weeks":
|
||||
return n + " week";
|
||||
case "month":
|
||||
case "months":
|
||||
return n + " month";
|
||||
case "year":
|
||||
case "years":
|
||||
return n + " year";
|
||||
case "alltime":
|
||||
return null;
|
||||
default:
|
||||
return '1 day';
|
||||
}
|
||||
}
|
||||
|
||||
function loadRate(asset) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof currentRate[asset] !== "undefined")
|
||||
return resolve(currentRate[asset]);
|
||||
updateLastTime(asset);
|
||||
DB.query("SELECT rate FROM PriceHistory WHERE asset=? ORDER BY rec_time DESC LIMIT 1", [asset]).then(result => {
|
||||
updateLastTime(asset);
|
||||
if (result.length)
|
||||
resolve(currentRate[asset] = result[0].rate);
|
||||
else
|
||||
DB.query("SELECT initialPrice FROM AssetList WHERE asset=?", [asset])
|
||||
.then(result => resolve(currentRate[asset] = result[0].initialPrice))
|
||||
.catch(error => reject(error))
|
||||
DB.query("SELECT initialPrice FROM AssetList WHERE asset=?", [asset]).then(result => {
|
||||
currentRate[asset] = result[0].initialPrice;
|
||||
storeHistory(asset, currentRate[asset]);
|
||||
resolve(currentRate[asset]);
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error));
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
function fetchRates() {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetchRates.FLO_USD().then(FLO_rate => {
|
||||
fetchRates.USD_INR().then(INR_rate => {
|
||||
let FLO_INR_rate = FLO_rate * INR_rate;
|
||||
console.debug('Rates:', FLO_rate, INR_rate, FLO_INR_rate);
|
||||
storeRate(FLO_INR_rate);
|
||||
resolve(FLO_INR_rate);
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
|
||||
fetchRates.FLO_USD = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch('https://api.coinlore.net/api/ticker/?id=67').then(response => {
|
||||
if (response.ok) {
|
||||
response.json()
|
||||
.then(result => resolve(result[0].price_usd))
|
||||
.catch(error => reject(error));
|
||||
} else
|
||||
reject(response.status);
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
fetchRates.USD_INR = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch('https://api.exchangerate-api.com/v4/latest/usd').then(response => {
|
||||
if (response.ok) {
|
||||
response.json()
|
||||
.then(result => resolve(result.rates['INR']))
|
||||
.catch(error => reject(error));
|
||||
} else
|
||||
reject(response.status);
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
function getRates(asset) {
|
||||
function getRates(asset, updatePrice = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
loadRate(asset).then(_ => {
|
||||
//console.debug(asset, currentRate[asset]);
|
||||
let cur_time = Date.now();
|
||||
if (cur_time - lastTime[asset] < MIN_TIME) //Minimum time to update not crossed: No update required
|
||||
if (!updatePrice || cur_time - lastTime[asset] < MIN_TIME) //Minimum time to update not crossed: No update required
|
||||
resolve(currentRate[asset]);
|
||||
else if (noBuyOrder[asset] && noSellOrder[asset]) //Both are not available: No update required
|
||||
resolve(currentRate[asset]);
|
||||
@ -110,10 +121,9 @@ function getRates(asset) {
|
||||
if (noBuyOrder[asset]) {
|
||||
//No Buy, But Sell available: Decrease the price
|
||||
let tmp_val = currentRate[asset] * (1 - DOWN_RATE);
|
||||
if (tmp_val >= ratePast24hr * (1 - MAX_DOWN_PER_DAY)) {
|
||||
if (tmp_val >= ratePast24hr * (1 - MAX_DOWN_PER_DAY))
|
||||
currentRate[asset] = tmp_val;
|
||||
updateLastTime(asset);
|
||||
} else
|
||||
else
|
||||
console.debug("Max Price down for the day has reached");
|
||||
resolve(currentRate[asset]);
|
||||
} else if (noSellOrder[asset]) {
|
||||
@ -121,10 +131,9 @@ function getRates(asset) {
|
||||
checkForRatedSellers(asset).then(result => {
|
||||
if (result) {
|
||||
let tmp_val = currentRate[asset] * (1 + UP_RATE);
|
||||
if (tmp_val <= ratePast24hr * (1 + MAX_UP_PER_DAY)) {
|
||||
if (tmp_val <= ratePast24hr * (1 + MAX_UP_PER_DAY))
|
||||
currentRate[asset] = tmp_val;
|
||||
updateLastTime(asset);
|
||||
} else
|
||||
else
|
||||
console.debug("Max Price up for the day has reached");
|
||||
}
|
||||
}).catch(error => console.error(error)).finally(_ => resolve(currentRate[asset]));
|
||||
@ -140,28 +149,36 @@ function getRates(asset) {
|
||||
function checkForRatedSellers(asset) {
|
||||
//Check if there are best rated sellers?
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!CHECK_RATED_SELLER) //switch for the check case
|
||||
return resolve(true);
|
||||
DB.query("SELECT MAX(sellPriority) as max_p FROM TagList").then(result => {
|
||||
let ratedMin = result[0].max_p * (1 - TOP_RANGE);
|
||||
DB.query("SELECT COUNT(*) as value FROM SellOrder WHERE floID IN (" +
|
||||
" SELECT UserTag.floID FROM UserTag INNER JOIN TagList ON UserTag.tag = TagList.tag" +
|
||||
" WHERE TagList.sellPriority > ?) AND asset=?", [ratedMin, asset]).then(result => {
|
||||
resolve(result[0].value > 0);
|
||||
}).catch(error => reject(error))
|
||||
resolve(result[0].value > 0);
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getRates,
|
||||
getHistory,
|
||||
storeHistory,
|
||||
updateLastTime,
|
||||
MIN_TIME,
|
||||
noOrder(asset, buy, sell) {
|
||||
noBuyOrder[asset] = buy;
|
||||
noSellOrder[asset] = sell;
|
||||
},
|
||||
set DB(db) {
|
||||
DB = db;
|
||||
},
|
||||
get currentRates() {
|
||||
return Object.assign({}, currentRate);
|
||||
},
|
||||
get lastTimes() {
|
||||
let countDown = {};
|
||||
for (let asset in lastTime)
|
||||
countDown[asset] = lastTime[asset] + MIN_TIME;
|
||||
return countDown;
|
||||
}
|
||||
}
|
||||
658
src/request.js
658
src/request.js
@ -1,21 +1,35 @@
|
||||
'use strict';
|
||||
const DB = require("./database");
|
||||
|
||||
const market = require("./market");
|
||||
const conversion = require('./services/conversion');
|
||||
const blockchain_bonds = require("./services/bonds");
|
||||
const bobs_fund = require("./services/bobs-fund");
|
||||
const background = require("./background");
|
||||
const sink = require("./backup/head").sink;
|
||||
const keys = require("./keys");
|
||||
|
||||
const {
|
||||
SIGN_EXPIRE_TIME,
|
||||
MAX_SESSION_TIMEOUT,
|
||||
INVALID_SERVER_MSG
|
||||
MAX_SESSION_TIMEOUT
|
||||
} = require("./_constants")["request"];
|
||||
|
||||
var DB, trustedIDs, secret; //container for database
|
||||
const eCode = require('../docs/scripts/floExchangeAPI').errorCode;
|
||||
const serviceList = require('../docs/scripts/floExchangeAPI').serviceList;
|
||||
|
||||
global.INVALID = function(message) {
|
||||
var trustedIDs, secret; //containers for trusted IDs and secret
|
||||
|
||||
global.INVALID = function (ecode, message) {
|
||||
if (!(this instanceof INVALID))
|
||||
return new INVALID(message);
|
||||
return new INVALID(ecode, message);
|
||||
this.message = message;
|
||||
this.ecode = ecode;
|
||||
}
|
||||
INVALID.e_code = 400;
|
||||
INVALID.prototype.toString = function () {
|
||||
return "E" + this.ecode + ": " + this.message;
|
||||
}
|
||||
INVALID.str = (ecode, message) => INVALID(ecode, message).toString();
|
||||
|
||||
global.INTERNAL = function INTERNAL(message) {
|
||||
if (!(this instanceof INTERNAL))
|
||||
@ -23,31 +37,37 @@ global.INTERNAL = function INTERNAL(message) {
|
||||
this.message = message;
|
||||
}
|
||||
INTERNAL.e_code = 500;
|
||||
INTERNAL.prototype.toString = function () {
|
||||
return "E" + eCode.INTERNAL_ERROR + ": " + this.message;
|
||||
}
|
||||
INTERNAL.str = (ecode, message) => INTERNAL(ecode, message).toString();
|
||||
|
||||
const INCORRECT_SERVER_ERROR = INVALID(eCode.INCORRECT_SERVER, "Incorrect server");
|
||||
|
||||
var serving;
|
||||
|
||||
function validateRequest(request, sign, floID, pubKey) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!serving)
|
||||
reject(INVALID(INVALID_SERVER_MSG));
|
||||
reject(INCORRECT_SERVER_ERROR);
|
||||
else if (!request.timestamp)
|
||||
reject(INVALID("Timestamp parameter missing"));
|
||||
reject(INVALID(eCode.MISSING_PARAMETER, "Timestamp parameter missing"));
|
||||
else if (Date.now() - SIGN_EXPIRE_TIME > request.timestamp)
|
||||
reject(INVALID("Signature Expired"));
|
||||
reject(INVALID(eCode.EXPIRED_SIGNATURE, "Signature Expired"));
|
||||
else if (!floCrypto.validateAddr(floID))
|
||||
reject(INVALID("Invalid floID"));
|
||||
reject(INVALID(eCode.INVALID_FLO_ID, "Invalid floID"));
|
||||
else if (typeof request !== "object")
|
||||
reject(INVALID("Request is not an object"));
|
||||
reject(INVALID(eCode.INVALID_REQUEST_FORMAT, "Request is not an object"));
|
||||
else validateRequest.getSignKey(floID, pubKey).then(signKey => {
|
||||
let req_str = Object.keys(request).sort().map(r => r + ":" + request[r]).join("|");
|
||||
try {
|
||||
if (!floCrypto.verifySign(req_str, sign, signKey))
|
||||
reject(INVALID("Invalid request signature! Re-login if this error occurs frequently"));
|
||||
reject(INVALID(eCode.INVALID_SIGNATURE, "Invalid request signature"));
|
||||
else validateRequest.checkIfSignUsed(sign)
|
||||
.then(result => resolve(req_str))
|
||||
.catch(error => reject(error))
|
||||
} catch {
|
||||
reject(INVALID("Corrupted sign/key"));
|
||||
reject(INVALID(eCode.INVALID_SIGNATURE, "Corrupted sign/key"));
|
||||
}
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
@ -57,22 +77,22 @@ validateRequest.getSignKey = (floID, pubKey) => new Promise((resolve, reject) =>
|
||||
if (!pubKey)
|
||||
DB.query("SELECT session_time, proxyKey FROM UserSession WHERE floID=?", [floID]).then(result => {
|
||||
if (result.length < 1)
|
||||
reject(INVALID("Session not active"));
|
||||
reject(INVALID(eCode.SESSION_INVALID, "Session not active"));
|
||||
else if (result[0].session_time + MAX_SESSION_TIMEOUT < Date.now())
|
||||
reject(INVALID("Session Expired! Re-login required"));
|
||||
reject(INVALID(eCode.SESSION_EXPIRED, "Session Expired! Re-login required"));
|
||||
else
|
||||
resolve(result[0].proxyKey);
|
||||
}).catch(error => reject(error));
|
||||
else if (floCrypto.getFloID(pubKey) === floID)
|
||||
resolve(pubKey);
|
||||
else
|
||||
reject(INVALID("Invalid pubKey"));
|
||||
reject(INVALID(eCode.INVALID_PUBLIC_KEY, "Invalid pubKey"));
|
||||
});
|
||||
|
||||
validateRequest.checkIfSignUsed = sign => new Promise((resolve, reject) => {
|
||||
DB.query("SELECT id FROM RequestLog WHERE sign=?", [sign]).then(result => {
|
||||
if (result.length)
|
||||
reject(INVALID("Duplicate signature"));
|
||||
reject(INVALID(eCode.DUPLICATE_SIGNATURE, "Duplicate signature"));
|
||||
else
|
||||
resolve(true);
|
||||
}).catch(error => reject(error))
|
||||
@ -80,29 +100,29 @@ validateRequest.checkIfSignUsed = sign => new Promise((resolve, reject) => {
|
||||
|
||||
function logRequest(floID, req_str, sign, proxy = false) {
|
||||
//console.debug(floID, req_str);
|
||||
DB.query("INSERT INTO RequestLog (floID, request, sign, proxy) VALUES (?,?,?, ?)", [floID, req_str, sign, proxy])
|
||||
DB.query("INSERT INTO RequestLog (floID, request, sign, proxy) VALUES (?)", [[floID, req_str, sign, proxy]])
|
||||
.then(_ => null).catch(error => console.error(error));
|
||||
}
|
||||
|
||||
function processRequest(res, rText, validateObj, sign, floID, pubKey, marketFn) {
|
||||
function processRequest(res, floID, pubKey, sign, rText, validateObj, marketFn, log = true) {
|
||||
validateRequest(validateObj, sign, floID, pubKey).then(req_str => {
|
||||
marketFn().then(result => {
|
||||
logRequest(floID, req_str, sign, !pubKey);
|
||||
if (log) logRequest(floID, req_str, sign, !pubKey);
|
||||
res.send(result);
|
||||
}).catch(error => {
|
||||
if (error instanceof INVALID)
|
||||
res.status(INVALID.e_code).send(error.message);
|
||||
res.status(INVALID.e_code).send(error.toString());
|
||||
else {
|
||||
console.error(error);
|
||||
res.status(INTERNAL.e_code).send(rText + " failed! Try again later!");
|
||||
res.status(INTERNAL.e_code).send(INTERNAL.str(rText + " failed! Try again later!"));
|
||||
}
|
||||
})
|
||||
}).catch(error => {
|
||||
if (error instanceof INVALID)
|
||||
res.status(INVALID.e_code).send(error.message);
|
||||
res.status(INVALID.e_code).send(error.toString());
|
||||
else {
|
||||
console.error(error);
|
||||
res.status(INTERNAL.e_code).send("Request processing failed! Try again later!");
|
||||
res.status(INTERNAL.e_code).send(INTERNAL.str("Request processing failed! Try again later!"));
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -116,17 +136,16 @@ function Account(req, res) {
|
||||
timestamp: data.timestamp
|
||||
}, data.sign, data.floID, data.pubKey).then(req_str => {
|
||||
market.getAccountDetails(data.floID).then(result => {
|
||||
result.sinkID = global.sinkID;
|
||||
if (trustedIDs.includes(data.floID))
|
||||
result.subAdmin = true;
|
||||
res.send(result);
|
||||
});
|
||||
}).catch(error => {
|
||||
if (error instanceof INVALID)
|
||||
res.status(INVALID.e_code).send(error.message);
|
||||
res.status(INVALID.e_code).send(error.toString());
|
||||
else {
|
||||
console.error(error);
|
||||
res.status(INTERNAL.e_code).send("Request processing failed! Try again later!");
|
||||
res.status(INTERNAL.e_code).send(INTERNAL.str("Request processing failed! Try again later!"));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -134,158 +153,332 @@ function Account(req, res) {
|
||||
function Login(req, res) {
|
||||
let data = req.body;
|
||||
if (!data.code || data.hash != Crypto.SHA1(data.code + secret))
|
||||
res.status(INVALID.e_code).send("Invalid Code");
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.INVALID_LOGIN_CODE, "Invalid Code"));
|
||||
else if (!data.pubKey)
|
||||
res.status(INVALID.e_code).send("Public key missing");
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.MISSING_PARAMETER, "Public key missing"));
|
||||
else
|
||||
processRequest(res, "Login", {
|
||||
type: "login",
|
||||
random: data.code,
|
||||
proxyKey: data.proxyKey,
|
||||
timestamp: data.timestamp
|
||||
}, data.sign, data.floID, data.pubKey,
|
||||
() => market.login(data.floID, data.proxyKey)
|
||||
);
|
||||
processRequest(res, data.floID, data.pubKey, data.sign, "Login", {
|
||||
type: "login",
|
||||
random: data.code,
|
||||
proxyKey: data.proxyKey,
|
||||
timestamp: data.timestamp
|
||||
}, () => market.login(data.floID, data.proxyKey));
|
||||
}
|
||||
|
||||
function Logout(req, res) {
|
||||
let data = req.body;
|
||||
processRequest(res, "Logout", {
|
||||
type: "logout",
|
||||
timestamp: data.timestamp
|
||||
}, data.sign, data.floID, data.pubKey,
|
||||
() => market.logout(data.floID)
|
||||
);
|
||||
processRequest(res, data.floID, data.pubKey, data.sign, "Logout", {
|
||||
type: "logout",
|
||||
timestamp: data.timestamp
|
||||
}, () => market.logout(data.floID));
|
||||
}
|
||||
|
||||
function PlaceSellOrder(req, res) {
|
||||
let data = req.body;
|
||||
processRequest(res, "Sell order placement", {
|
||||
type: "sell_order",
|
||||
asset: data.asset,
|
||||
quantity: data.quantity,
|
||||
min_price: data.min_price,
|
||||
timestamp: data.timestamp
|
||||
}, data.sign, data.floID, data.pubKey,
|
||||
() => market.addSellOrder(data.floID, data.asset, data.quantity, data.min_price)
|
||||
);
|
||||
processRequest(res, data.floID, data.pubKey, data.sign, "Sell order placement", {
|
||||
type: "sell_order",
|
||||
asset: data.asset,
|
||||
quantity: data.quantity,
|
||||
min_price: data.min_price,
|
||||
timestamp: data.timestamp
|
||||
}, () => market.addSellOrder(data.floID, data.asset, data.quantity, data.min_price));
|
||||
}
|
||||
|
||||
function PlaceBuyOrder(req, res) {
|
||||
let data = req.body;
|
||||
processRequest(res, "Buy order placement", {
|
||||
type: "buy_order",
|
||||
asset: data.asset,
|
||||
quantity: data.quantity,
|
||||
max_price: data.max_price,
|
||||
timestamp: data.timestamp
|
||||
}, data.sign, data.floID, data.pubKey,
|
||||
() => market.addBuyOrder(data.floID, data.asset, data.quantity, data.max_price)
|
||||
);
|
||||
processRequest(res, data.floID, data.pubKey, data.sign, "Buy order placement", {
|
||||
type: "buy_order",
|
||||
asset: data.asset,
|
||||
quantity: data.quantity,
|
||||
max_price: data.max_price,
|
||||
timestamp: data.timestamp
|
||||
}, () => market.addBuyOrder(data.floID, data.asset, data.quantity, data.max_price));
|
||||
}
|
||||
|
||||
function CancelOrder(req, res) {
|
||||
let data = req.body;
|
||||
processRequest(res, "Order cancellation", {
|
||||
type: "cancel_order",
|
||||
order: data.orderType,
|
||||
id: data.orderID,
|
||||
timestamp: data.timestamp
|
||||
}, data.sign, data.floID, data.pubKey,
|
||||
() => market.cancelOrder(data.orderType, data.orderID, data.floID)
|
||||
);
|
||||
processRequest(res, data.floID, data.pubKey, data.sign, "Order cancellation", {
|
||||
type: "cancel_order",
|
||||
order: data.orderType,
|
||||
id: data.orderID,
|
||||
timestamp: data.timestamp
|
||||
}, () => market.cancelOrder(data.orderType, data.orderID, data.floID));
|
||||
}
|
||||
|
||||
function TransferToken(req, res) {
|
||||
let data = req.body;
|
||||
processRequest(res, "Token Transfer", {
|
||||
type: "transfer_token",
|
||||
receiver: JSON.stringify(data.receiver),
|
||||
token: data.token,
|
||||
timestamp: data.timestamp
|
||||
}, data.sign, data.floID, data.pubKey,
|
||||
() => market.transferToken(data.floID, data.receiver, data.token)
|
||||
);
|
||||
processRequest(res, data.floID, data.pubKey, data.sign, "Token Transfer", {
|
||||
type: "transfer_token",
|
||||
receiver: JSON.stringify(data.receiver),
|
||||
token: data.token,
|
||||
timestamp: data.timestamp
|
||||
}, () => market.transferToken(data.floID, data.receiver, data.token));
|
||||
}
|
||||
|
||||
function DepositFLO(req, res) {
|
||||
let data = req.body;
|
||||
processRequest(res, "Deposit FLO", {
|
||||
type: "deposit_flo",
|
||||
txid: data.txid,
|
||||
timestamp: data.timestamp
|
||||
}, data.sign, data.floID, data.pubKey,
|
||||
() => market.depositFLO(data.floID, data.txid)
|
||||
);
|
||||
processRequest(res, data.floID, data.pubKey, data.sign, "Deposit FLO", {
|
||||
type: "deposit_flo",
|
||||
txid: data.txid,
|
||||
timestamp: data.timestamp
|
||||
}, () => market.depositFLO(data.floID, data.txid));
|
||||
}
|
||||
|
||||
function WithdrawFLO(req, res) {
|
||||
let data = req.body;
|
||||
processRequest(res, "Withdraw FLO", {
|
||||
type: "withdraw_flo",
|
||||
amount: data.amount,
|
||||
timestamp: data.timestamp
|
||||
}, data.sign, data.floID, data.pubKey,
|
||||
() => market.withdrawFLO(data.floID, data.amount)
|
||||
);
|
||||
processRequest(res, data.floID, data.pubKey, data.sign, "Withdraw FLO", {
|
||||
type: "withdraw_flo",
|
||||
amount: data.amount,
|
||||
timestamp: data.timestamp
|
||||
}, () => market.withdrawFLO(data.floID, data.amount));
|
||||
}
|
||||
|
||||
function DepositToken(req, res) {
|
||||
let data = req.body;
|
||||
processRequest(res, "Deposit Token", {
|
||||
type: "deposit_token",
|
||||
txid: data.txid,
|
||||
timestamp: data.timestamp
|
||||
}, data.sign, data.floID, data.pubKey,
|
||||
() => market.depositToken(data.floID, data.txid)
|
||||
);
|
||||
processRequest(res, data.floID, data.pubKey, data.sign, "Deposit Token", {
|
||||
type: "deposit_token",
|
||||
txid: data.txid,
|
||||
timestamp: data.timestamp
|
||||
}, () => market.depositToken(data.floID, data.txid));
|
||||
}
|
||||
|
||||
function WithdrawToken(req, res) {
|
||||
let data = req.body;
|
||||
processRequest(res, "Withdraw Token", {
|
||||
type: "withdraw_token",
|
||||
token: data.token,
|
||||
amount: data.amount,
|
||||
timestamp: data.timestamp
|
||||
}, data.sign, data.floID, data.pubKey,
|
||||
() => market.withdrawToken(data.floID, data.token, data.amount)
|
||||
);
|
||||
processRequest(res, data.floID, data.pubKey, data.sign, "Withdraw Token", {
|
||||
type: "withdraw_token",
|
||||
token: data.token,
|
||||
amount: data.amount,
|
||||
timestamp: data.timestamp
|
||||
}, () => market.withdrawToken(data.floID, data.token, data.amount));
|
||||
}
|
||||
|
||||
function GetUserTransacts(req, res) {
|
||||
let data = req.body;
|
||||
processRequest(res, data.floID, data.pubKey, data.sign, "User Transacts", {
|
||||
type: "get_transact",
|
||||
timestamp: data.timestamp
|
||||
}, () => market.getUserTransacts(data.floID));
|
||||
}
|
||||
|
||||
function AddUserTag(req, res) {
|
||||
let data = req.body;
|
||||
if (!trustedIDs.includes(data.floID))
|
||||
res.status(INVALID.e_code).send("Access Denied");
|
||||
else processRequest(res, "Add user-tag", {
|
||||
type: "add_tag",
|
||||
user: data.user,
|
||||
tag: data.tag,
|
||||
timestamp: data.timestamp
|
||||
}, data.sign, data.floID, data.pubKey,
|
||||
() => market.group.addTag(data.user, data.tag)
|
||||
);
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied"));
|
||||
else processRequest(res, data.floID, data.pubKey, data.sign, "Add user-tag", {
|
||||
type: "add_tag",
|
||||
user: data.user,
|
||||
tag: data.tag,
|
||||
timestamp: data.timestamp
|
||||
}, () => market.addTag(data.user, data.tag));
|
||||
}
|
||||
|
||||
function RemoveUserTag(req, res) {
|
||||
let data = req.body;
|
||||
if (!trustedIDs.includes(data.floID))
|
||||
res.status(INVALID.e_code).send("Access Denied");
|
||||
else processRequest(res, "Remove user-tag", {
|
||||
type: "remove_tag",
|
||||
user: data.user,
|
||||
tag: data.tag,
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied"));
|
||||
else processRequest(res, data.floID, data.pubKey, data.sign, "Remove user-tag", {
|
||||
type: "remove_tag",
|
||||
user: data.user,
|
||||
tag: data.tag,
|
||||
timestamp: data.timestamp
|
||||
}, () => market.removeTag(data.user, data.tag));
|
||||
}
|
||||
|
||||
function AddDistributor(req, res) {
|
||||
let data = req.body;
|
||||
if (!trustedIDs.includes(data.floID))
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied"));
|
||||
else processRequest(res, data.floID, data.pubKey, data.sign, "Add distributor", {
|
||||
type: "add_distributor",
|
||||
distributor: data.distributor,
|
||||
asset: data.asset,
|
||||
timestamp: data.timestamp
|
||||
}, () => market.addDistributor(data.distributor, data.asset));
|
||||
}
|
||||
|
||||
function RemoveDistributor(req, res) {
|
||||
let data = req.body;
|
||||
if (!trustedIDs.includes(data.floID))
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied"));
|
||||
else processRequest(res, data.floID, data.pubKey, data.sign, "Remove distributor", {
|
||||
type: "remove_distributor",
|
||||
distributor: data.distributor,
|
||||
asset: data.asset,
|
||||
timestamp: data.timestamp
|
||||
}, () => market.removeDistributor(data.distributor, data.asset));
|
||||
}
|
||||
|
||||
function GenerateSink(req, res) {
|
||||
let data = req.body;
|
||||
if (data.floID !== floGlobals.adminID)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied"));
|
||||
else if (!data.pubKey)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.MISSING_PARAMETER, "Public key missing"));
|
||||
else processRequest(res, data.floID, data.pubKey, data.sign, "Generate Sink", {
|
||||
type: "generate_sink",
|
||||
group: data.group,
|
||||
timestamp: data.timestamp
|
||||
}, () => sink.generate(data.group));
|
||||
}
|
||||
|
||||
function ReshareSink(req, res) {
|
||||
let data = req.body;
|
||||
console.debug(data)
|
||||
if (data.floID !== floGlobals.adminID)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied"));
|
||||
else if (!data.pubKey)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.MISSING_PARAMETER, "Public key missing"));
|
||||
else if (!floCrypto.validateAddr(data.id))
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.INVALID_VALUE, `Invalid ID ${data.id}`));
|
||||
else processRequest(res, data.floID, data.pubKey, data.sign, "Reshare Sink", {
|
||||
type: "reshare_sink",
|
||||
id: data.id,
|
||||
timestamp: data.timestamp
|
||||
}, () => sink.reshare(data.id));
|
||||
}
|
||||
|
||||
function DiscardSink(req, res) {
|
||||
let data = req.body;
|
||||
if (data.floID !== floGlobals.adminID)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied"));
|
||||
else if (!data.pubKey)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.MISSING_PARAMETER, "Public key missing"));
|
||||
else processRequest(res, data.floID, data.pubKey, data.sign, "Discard Sink", {
|
||||
type: "discard_sink",
|
||||
id: data.id,
|
||||
timestamp: data.timestamp
|
||||
}, () => sink.discard(data.id));
|
||||
}
|
||||
|
||||
function ConvertTo(req, res) {
|
||||
let data = req.body;
|
||||
processRequest(res, data.floID, data.pubKey, data.sign, "Conversion", {
|
||||
type: "convert_to",
|
||||
coin: data.coin,
|
||||
amount: data.amount,
|
||||
txid: data.txid,
|
||||
timestamp: data.timestamp
|
||||
}, () => conversion.convertToCoin(data.floID, data.txid, data.coin, data.amount));
|
||||
}
|
||||
|
||||
function ConvertFrom(req, res) {
|
||||
let data = req.body;
|
||||
processRequest(res, data.floID, data.pubKey, data.sign, "Conversion", {
|
||||
type: "convert_from",
|
||||
coin: data.coin,
|
||||
quantity: data.quantity,
|
||||
txid: data.txid,
|
||||
timestamp: data.timestamp
|
||||
}, () => conversion.convertFromCoin(data.floID, data.txid, data.tx_hex, data.coin, data.quantity));
|
||||
}
|
||||
|
||||
function DepositConvertCoinFund(req, res) {
|
||||
let data = req.body;
|
||||
if (data.floID !== floGlobals.adminID)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied"));
|
||||
else if (!data.pubKey)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.MISSING_PARAMETER, "Public key missing"));
|
||||
else processRequest(res, data.floID, data.pubKey, data.sign, "Conversion Fund", {
|
||||
type: "deposit_convert_coin_fund",
|
||||
coin: data.coin,
|
||||
txid: data.txid,
|
||||
timestamp: data.timestamp
|
||||
}, () => conversion.depositFund.coin(data.floID, data.txid, data.coin));
|
||||
}
|
||||
|
||||
function DepositConvertCurrencyFund(req, res) {
|
||||
let data = req.body;
|
||||
if (data.floID !== floGlobals.adminID)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied"));
|
||||
else if (!data.pubKey)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.MISSING_PARAMETER, "Public key missing"));
|
||||
else processRequest(res, data.floID, data.pubKey, data.sign, "Conversion Fund", {
|
||||
type: "deposit_convert_currency_fund",
|
||||
coin: data.coin,
|
||||
txid: data.txid,
|
||||
timestamp: data.timestamp
|
||||
}, () => conversion.depositFund.currency(data.floID, data.txid, data.coin));
|
||||
}
|
||||
|
||||
function WithdrawConvertCoinFund(req, res) {
|
||||
let data = req.body;
|
||||
if (data.floID !== floGlobals.adminID)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied"));
|
||||
else if (!data.pubKey)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.MISSING_PARAMETER, "Public key missing"));
|
||||
else processRequest(res, data.floID, data.pubKey, data.sign, "Conversion Fund", {
|
||||
type: "withdraw_convert_coin_fund",
|
||||
coin: data.coin,
|
||||
quantity: data.quantity,
|
||||
timestamp: data.timestamp
|
||||
}, () => conversion.withdrawFund.coin(data.floID, data.coin, data.quantity));
|
||||
}
|
||||
|
||||
function WithdrawConvertCurrencyFund(req, res) {
|
||||
let data = req.body;
|
||||
if (data.floID !== floGlobals.adminID)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied"));
|
||||
else if (!data.pubKey)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.MISSING_PARAMETER, "Public key missing"));
|
||||
else processRequest(res, data.floID, data.pubKey, data.sign, "Conversion Fund", {
|
||||
type: "withdraw_convert_currency_fund",
|
||||
coin: data.coin,
|
||||
amount: data.amount,
|
||||
timestamp: data.timestamp
|
||||
}, () => conversion.withdrawFund.currency(data.floID, data.coin, data.amount));
|
||||
}
|
||||
|
||||
function CloseBlockchainBond(req, res) {
|
||||
let data = req.body;
|
||||
if (!data.pubKey)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.MISSING_PARAMETER, "Public key missing"));
|
||||
else
|
||||
processRequest(res, data.floID, data.pubKey, data.sign, "Blockchain Bond Closing", {
|
||||
type: "close_blockchain_bond",
|
||||
bond_id: data.bond_id,
|
||||
timestamp: data.timestamp
|
||||
}, data.sign, data.floID, data.pubKey,
|
||||
() => market.group.removeTag(data.user, data.tag)
|
||||
);
|
||||
}, () => blockchain_bonds.closeBond(data.bond_id, data.floID, `${data.timestamp}.${data.sign}`));
|
||||
}
|
||||
|
||||
function CloseBobsFund(req, res) {
|
||||
let data = req.body;
|
||||
if (!data.pubKey)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.MISSING_PARAMETER, "Public key missing"));
|
||||
else
|
||||
processRequest(res, data.floID, data.pubKey, data.sign, "Bob's Fund closing", {
|
||||
type: "close_bobs_fund",
|
||||
fund_id: data.fund_id,
|
||||
timestamp: data.timestamp
|
||||
}, () => bobs_fund.closeFund(data.fund_id, data.floID, `${data.timestamp}.${data.sign}`));
|
||||
}
|
||||
|
||||
function CheckBlockchainBondBalance(req, res) {
|
||||
let data = req.body;
|
||||
if (!trustedIDs.includes(data.floID))
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied"));
|
||||
else processRequest(res, data.floID, data.pubKey, data.sign, "Check blockchain-bond", {
|
||||
type: "check_blockchain_bond",
|
||||
prior_time: data.prior_time,
|
||||
timestamp: data.timestamp
|
||||
}, () => blockchain_bonds.checkBondBalance(data.prior_time), false);
|
||||
}
|
||||
|
||||
function CheckBobsFundBalance(req, res) {
|
||||
let data = req.body;
|
||||
if (!trustedIDs.includes(data.floID))
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied"));
|
||||
else processRequest(res, data.floID, data.pubKey, data.sign, "Check bobs-fund", {
|
||||
type: "check_bobs_fund",
|
||||
prior_time: data.prior_time,
|
||||
timestamp: data.timestamp
|
||||
}, () => bobs_fund.checkFundBalance(data.prior_time), false);
|
||||
}
|
||||
|
||||
/* Public Requests */
|
||||
|
||||
function GetLoginCode(req, res) {
|
||||
if (!serving)
|
||||
res.status(INVALID.e_code).send(INVALID_SERVER_MSG);
|
||||
res.status(INVALID.e_code).send(INCORRECT_SERVER_ERROR.toString());
|
||||
else {
|
||||
let randID = floCrypto.randString(8, true) + Math.round(Date.now() / 1000);
|
||||
let hash = Crypto.SHA1(randID + secret);
|
||||
@ -297,58 +490,171 @@ function GetLoginCode(req, res) {
|
||||
}
|
||||
|
||||
function ListSellOrders(req, res) {
|
||||
//TODO: Limit size (best)
|
||||
DB.query("SELECT * FROM SellOrder ORDER BY time_placed")
|
||||
.then(result => res.send(result))
|
||||
.catch(error => res.status(INTERNAL.e_code).send("Try again later!"));
|
||||
}
|
||||
|
||||
function ListBuyOrders(req, res) {
|
||||
//TODO: Limit size (best)
|
||||
DB.query("SELECT * FROM BuyOrder ORDER BY time_placed")
|
||||
.then(result => res.send(result))
|
||||
.catch(error => res.status(INTERNAL.e_code).send("Try again later!"));
|
||||
}
|
||||
|
||||
function ListTradeTransactions(req, res) {
|
||||
//TODO: Limit size (recent)
|
||||
DB.query("SELECT * FROM TradeTransactions ORDER BY tx_time DESC")
|
||||
.then(result => res.send(result))
|
||||
.catch(error => res.status(INTERNAL.e_code).send("Try again later!"));
|
||||
}
|
||||
|
||||
function GetRates(req, res) {
|
||||
if (!serving)
|
||||
res.status(INVALID.e_code).send(INVALID_SERVER_MSG);
|
||||
res.status(INVALID.e_code).send(INCORRECT_SERVER_ERROR.toString());
|
||||
else {
|
||||
let asset = req.query.asset,
|
||||
rates = market.rates;
|
||||
if (asset) {
|
||||
if (asset in rates)
|
||||
res.send(rates[asset].toString());
|
||||
else
|
||||
res.status(INVALID.e_code).send("Invalid asset parameter");
|
||||
} else
|
||||
res.send(rates);
|
||||
let asset = req.query.asset;
|
||||
if (asset && !market.assetList.includes(asset))
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.INVALID_TOKEN_NAME, "Invalid asset parameter"));
|
||||
else
|
||||
DB.query("SELECT SellOrder.floID, SellOrder.asset, SellOrder.minPrice, SellOrder.quantity, SellOrder.time_placed FROM SellOrder" +
|
||||
" INNER JOIN UserBalance ON UserBalance.floID = SellOrder.floID AND UserBalance.token = SellOrder.asset" +
|
||||
" INNER JOIN SellChips ON SellChips.floID = SellOrder.floID AND SellChips.asset = SellOrder.asset" +
|
||||
" LEFT JOIN UserTag ON UserTag.floID = SellOrder.floID" +
|
||||
" LEFT JOIN TagList ON TagList.tag = UserTag.tag" +
|
||||
" WHERE UserBalance.quantity >= SellOrder.quantity" +
|
||||
(asset ? " AND SellOrder.asset = ?" : "") +
|
||||
" GROUP BY SellOrder.id" +
|
||||
" ORDER BY MAX(TagList.sellPriority) DESC, MIN(SellChips.locktime) ASC, SellOrder.time_placed ASC" +
|
||||
" LIMIT 100", [asset || null])
|
||||
.then(result => res.send(result))
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
res.status(INTERNAL.e_code).send(INTERNAL.str("Try again later!"));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function ListBuyOrders(req, res) {
|
||||
if (!serving)
|
||||
res.status(INVALID.e_code).send(INCORRECT_SERVER_ERROR.toString());
|
||||
else {
|
||||
let asset = req.query.asset;
|
||||
if (asset && !market.assetList.includes(asset))
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.INVALID_TOKEN_NAME, "Invalid asset parameter"));
|
||||
else
|
||||
DB.query("SELECT BuyOrder.floID, BuyOrder.asset, BuyOrder.maxPrice, BuyOrder.quantity, BuyOrder.time_placed FROM BuyOrder" +
|
||||
" INNER JOIN UserBalance ON UserBalance.floID = BuyOrder.floID AND UserBalance.token = ?" +
|
||||
" LEFT JOIN UserTag ON UserTag.floID = BuyOrder.floID" +
|
||||
" LEFT JOIN TagList ON TagList.tag = UserTag.tag" +
|
||||
" WHERE UserBalance.quantity >= BuyOrder.maxPrice * BuyOrder.quantity" +
|
||||
(asset ? " AND BuyOrder.asset = ?" : "") +
|
||||
" GROUP BY BuyOrder.id" +
|
||||
" ORDER BY MAX(TagList.buyPriority) DESC, BuyOrder.time_placed ASC" +
|
||||
" LIMIT 100", [floGlobals.currency, asset || null])
|
||||
.then(result => res.send(result))
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
res.status(INTERNAL.e_code).send(INTERNAL.str("Try again later!"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function ListTradeTransactions(req, res) {
|
||||
if (!serving)
|
||||
res.status(INVALID.e_code).send(INCORRECT_SERVER_ERROR.toString());
|
||||
else {
|
||||
let asset = req.query.asset;
|
||||
if (asset && !market.assetList.includes(asset))
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.INVALID_TOKEN_NAME, "Invalid asset parameter"));
|
||||
else
|
||||
DB.query("SELECT * FROM TradeTransactions" +
|
||||
(asset ? " WHERE asset = ?" : "") +
|
||||
" ORDER BY tx_time DESC LIMIT 1000", [asset || null])
|
||||
.then(result => res.send(result))
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
res.status(INTERNAL.e_code).send(INTERNAL.str("Try again later!"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function GetConvertValues(req, res) {
|
||||
if (!serving)
|
||||
res.status(INVALID.e_code).send(INCORRECT_SERVER_ERROR.toString());
|
||||
else conversion.getConvertValues()
|
||||
.then(result => res.send(result))
|
||||
.catch(error => {
|
||||
if (error instanceof INVALID)
|
||||
res.status(INVALID.e_code).send(error.toString());
|
||||
else {
|
||||
console.error(error);
|
||||
res.status(INTERNAL.e_code).send(INTERNAL.str("Unable to process! Try again later!"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function GetSink(req, res) {
|
||||
if (!serving)
|
||||
res.status(INVALID.e_code).send(INCORRECT_SERVER_ERROR.toString());
|
||||
else {
|
||||
let service = req.query.service;
|
||||
if (!service)
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.MISSING_PARAMETER, "Missing service parameter"));
|
||||
else if (!(Object.values(serviceList).includes(service)))
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.INVALID_VALUE, "Invalid service parameter"));
|
||||
else {
|
||||
let group;
|
||||
switch (service) {
|
||||
case serviceList.EXCHANGE: group = keys.sink_groups.EXCHANGE; break;
|
||||
case serviceList.CONVERT: group = keys.sink_groups.CONVERT; break;
|
||||
case serviceList.BLOCKCHAIN_BOND: group = keys.sink_groups.BLOCKCHAIN_BONDS; break;
|
||||
case serviceList.BOBS_FUND: group = keys.sink_groups.BOBS_FUND; break;
|
||||
}
|
||||
res.send(keys.sink_chest.active_pick(group));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function GetRates(req, res) {
|
||||
if (!serving)
|
||||
res.status(INVALID.e_code).send(INCORRECT_SERVER_ERROR.toString());
|
||||
else {
|
||||
let asset = req.query.asset,
|
||||
rates = market.rates,
|
||||
countDown = market.priceCountDown;
|
||||
if (asset) {
|
||||
if (asset in rates)
|
||||
res.send({
|
||||
asset: asset,
|
||||
rate: rates[asset],
|
||||
countDown: countDown[asset]
|
||||
});
|
||||
else
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.INVALID_TOKEN_NAME, "Invalid asset parameter"));
|
||||
} else
|
||||
res.send({
|
||||
rates,
|
||||
countDown
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function GetRateHistory(req, res) {
|
||||
if (!serving)
|
||||
res.status(INVALID.e_code).send(INCORRECT_SERVER_ERROR.toString());
|
||||
else {
|
||||
let asset = req.query.asset,
|
||||
duration = req.query.duration || "";
|
||||
market.getRateHistory(asset, duration)
|
||||
.then(result => res.send(result))
|
||||
.catch(error => {
|
||||
if (error instanceof INVALID)
|
||||
res.status(INVALID.e_code).send(error.toString());
|
||||
else {
|
||||
console.error(error);
|
||||
res.status(INTERNAL.e_code).send(INTERNAL.str("Unable to process! Try again later!"));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function GetTransaction(req, res) {
|
||||
if (!serving)
|
||||
res.status(INVALID.e_code).send(INVALID_SERVER_MSG);
|
||||
res.status(INVALID.e_code).send(INCORRECT_SERVER_ERROR.toString());
|
||||
else {
|
||||
let txid = req.query.txid;
|
||||
if (!txid)
|
||||
res.status(INVALID.e_code).send("txid (transactionID) parameter missing");
|
||||
res.status(INVALID.e_code).send(INVALID.str(eCode.MISSING_PARAMETER, "txid (transactionID) parameter missing"));
|
||||
else market.getTransactionDetails(txid)
|
||||
.then(result => res.send(result))
|
||||
.catch(error => {
|
||||
if (error instanceof INVALID)
|
||||
res.status(INVALID.e_code).send(error.message);
|
||||
res.status(INVALID.e_code).send(error.toString());
|
||||
else {
|
||||
console.error(error);
|
||||
res.status(INTERNAL.e_code).send("Unable to process! Try again later!");
|
||||
res.status(INTERNAL.e_code).send(INTERNAL.str("Unable to process! Try again later!"));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -356,7 +662,7 @@ function GetTransaction(req, res) {
|
||||
|
||||
function GetBalance(req, res) {
|
||||
if (!serving)
|
||||
res.status(INVALID.e_code).send(INVALID_SERVER_MSG);
|
||||
res.status(INVALID.e_code).send(INCORRECT_SERVER_ERROR.toString());
|
||||
else {
|
||||
let floID = req.query.floID || req.query.addr,
|
||||
token = req.query.token || req.query.asset;
|
||||
@ -364,10 +670,10 @@ function GetBalance(req, res) {
|
||||
.then(result => res.send(result))
|
||||
.catch(error => {
|
||||
if (error instanceof INVALID)
|
||||
res.status(INVALID.e_code).send(error.message);
|
||||
res.status(INVALID.e_code).send(error.toString());
|
||||
else {
|
||||
console.error(error);
|
||||
res.status(INTERNAL.e_code).send("Unable to process! Try again later!");
|
||||
res.status(INTERNAL.e_code).send(INTERNAL.str("Unable to process! Try again later!"));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -385,33 +691,53 @@ module.exports = {
|
||||
ListBuyOrders,
|
||||
ListTradeTransactions,
|
||||
GetRates,
|
||||
GetRateHistory,
|
||||
GetTransaction,
|
||||
GetBalance,
|
||||
GetSink,
|
||||
Account,
|
||||
DepositFLO,
|
||||
WithdrawFLO,
|
||||
DepositToken,
|
||||
WithdrawToken,
|
||||
periodicProcess: market.periodicProcess,
|
||||
GetUserTransacts,
|
||||
AddUserTag,
|
||||
RemoveUserTag,
|
||||
AddDistributor,
|
||||
RemoveDistributor,
|
||||
GenerateSink,
|
||||
ReshareSink,
|
||||
DiscardSink,
|
||||
GetConvertValues,
|
||||
ConvertTo,
|
||||
ConvertFrom,
|
||||
DepositConvertCoinFund,
|
||||
DepositConvertCurrencyFund,
|
||||
WithdrawConvertCoinFund,
|
||||
WithdrawConvertCurrencyFund,
|
||||
CloseBlockchainBond,
|
||||
CloseBobsFund,
|
||||
CheckBlockchainBondBalance,
|
||||
CheckBobsFundBalance,
|
||||
set trustedIDs(ids) {
|
||||
trustedIDs = ids;
|
||||
},
|
||||
set assetList(assets) {
|
||||
market.assetList = assets;
|
||||
},
|
||||
set DB(db) {
|
||||
DB = db;
|
||||
market.DB = db;
|
||||
},
|
||||
set secret(s) {
|
||||
secret = s;
|
||||
},
|
||||
refreshData(nodeList) {
|
||||
blockchain_bonds.refresh(nodeList);
|
||||
bobs_fund.refresh(nodeList);
|
||||
},
|
||||
pause() {
|
||||
serving = false;
|
||||
background.periodicProcess.stop();
|
||||
},
|
||||
resume() {
|
||||
serving = true;
|
||||
background.periodicProcess.start();
|
||||
}
|
||||
};
|
||||
358
src/services/bobs-fund.js
Normal file
358
src/services/bobs-fund.js
Normal file
@ -0,0 +1,358 @@
|
||||
'use strict';
|
||||
|
||||
const DB = require("../database");
|
||||
const { sink_chest, sink_groups } = require("../keys");
|
||||
const eCode = require('../../docs/scripts/floExchangeAPI').errorCode;
|
||||
const pCode = require('../../docs/scripts/floExchangeAPI').processCode;
|
||||
const getRate = require('./conversion').getRate;
|
||||
|
||||
const bobsFund = (function () {
|
||||
const productStr = "Bobs Fund";
|
||||
|
||||
const magnitude = m => {
|
||||
switch (m) {
|
||||
case "thousand": return 1000;
|
||||
case "lakh": case "lakhs": return 100000;
|
||||
case "million": return 1000000;
|
||||
case "crore": case "crores": return 10000000;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
const parseNumber = (str) => {
|
||||
let n = 0,
|
||||
g = 0;
|
||||
str.toLowerCase().replace(/,/g, '').split(" ").forEach(s => {
|
||||
if (!isNaN(s))
|
||||
g = parseFloat(s);
|
||||
else {
|
||||
let m = magnitude(s);
|
||||
if (m !== null) {
|
||||
n += m * g;
|
||||
g = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
return n + g;
|
||||
}
|
||||
const parsePeriod = (str) => {
|
||||
let P = '', n = 0;
|
||||
str.toLowerCase().replace(/,/g, '').split(" ").forEach(s => {
|
||||
if (!isNaN(s))
|
||||
n = parseFloat(s);
|
||||
else switch (s) {
|
||||
case "year(s)": case "year": case "years": P += (n + 'Y'); n = 0; break;
|
||||
case "month(s)": case "month": case "months": P += (n + 'M'); n = 0; break;
|
||||
case "day(s)": case "day": case "days": P += (n + 'D'); n = 0; break;
|
||||
}
|
||||
});
|
||||
return P;
|
||||
}
|
||||
const dateFormat = (date = null) => {
|
||||
let d = (date ? new Date(date) : new Date()).toDateString();
|
||||
return [d.substring(8, 10), d.substring(4, 7), d.substring(11, 15)].join(" ");
|
||||
}
|
||||
|
||||
const dateAdder = function (start_date, duration) {
|
||||
let date = new Date(start_date);
|
||||
let y = parseInt(duration.match(/\d+Y/)),
|
||||
m = parseInt(duration.match(/\d+M/)),
|
||||
d = parseInt(duration.match(/\d+D/));
|
||||
if (!isNaN(y))
|
||||
date.setFullYear(date.getFullYear() + y);
|
||||
if (!isNaN(m))
|
||||
date.setMonth(date.getMonth() + m);
|
||||
if (!isNaN(d))
|
||||
date.setDate(date.getDate() + d);
|
||||
return date;
|
||||
}
|
||||
|
||||
function calcNetValue(BTC_base, BTC_net, USD_base, USD_net, amount, fee) {
|
||||
let gain, interest, net;
|
||||
gain = (BTC_net - BTC_base) / BTC_base;
|
||||
interest = gain * (1 - fee)
|
||||
net = amount / USD_base;
|
||||
net += net * interest;
|
||||
return net * USD_net;
|
||||
}
|
||||
|
||||
function stringify_main(BTC_base, USD_base, start_date, duration, investments, fee = 0, tapoutWindow = null, tapoutInterval = null) {
|
||||
let result = [
|
||||
`${productStr}`,
|
||||
`Base Value: ${BTC_base} USD`,
|
||||
`USD INR rate at start: ${USD_base}`,
|
||||
`Start date: ${dateFormat(start_date)}`,
|
||||
`Duration: ${duration}`,
|
||||
`Management Fee: ${fee != 0 ? fee + "%" : "0 (Zero)"}`
|
||||
];
|
||||
if (tapoutInterval) {
|
||||
if (Array.isArray(tapoutInterval)) {
|
||||
let x = tapoutInterval.pop(),
|
||||
y = tapoutInterval.join(", ")
|
||||
tapoutInterval = `${y} and ${x}`
|
||||
}
|
||||
result.push(`Tapout availability: ${tapoutWindow} after ${tapoutInterval}`);
|
||||
}
|
||||
result.push(`Investment(s) (INR): ${investments.map(f => `${f[0].trim()}-${f[1].trim()}`).join("; ")}`);
|
||||
return result.join("|");
|
||||
}
|
||||
|
||||
function stringify_continue(fund_id, investments) {
|
||||
return [
|
||||
`${productStr}`,
|
||||
`continue: ${fund_id}`,
|
||||
`Investment(s) (INR): ${investments.map(f => `${f[0].trim()}-${f[1].trim()}`).join("; ")}`
|
||||
].join("|");
|
||||
}
|
||||
|
||||
function stringify_end(fund_id, floID, end_date, BTC_net, USD_net, amount, ref_sign, payment_ref) {
|
||||
return [
|
||||
`${productStr}`,
|
||||
`close: ${fund_id}`,
|
||||
`Investor: ${floID}`,
|
||||
`End value: ${BTC_net} USD`,
|
||||
`Date of withdrawal: ${dateFormat(end_date)}`,
|
||||
`USD INR rate at end: ${USD_net}`,
|
||||
`Amount withdrawn: Rs ${amount} via ${payment_ref}`,
|
||||
`Reference: ${ref_sign}`
|
||||
].join("|");
|
||||
}
|
||||
|
||||
function parse_details(data) {
|
||||
let funds = {};
|
||||
funds.investments = {};
|
||||
if (!Array.isArray(data))
|
||||
data = [data];
|
||||
data.forEach(fd => {
|
||||
if (!/close: [a-z0-9]{64}\|/.test(fd)) { // not a closing tx
|
||||
let cont = /continue: [a-z0-9]{64}\|/.test(fd);
|
||||
fd.split("|").forEach(d => {
|
||||
d = d.split(': ');
|
||||
if (["invesment(s) (inr)", "investment(s) (inr)"].includes(d[0].toLowerCase()))
|
||||
d[1].split(";").forEach(a => {
|
||||
a = a.split("-");
|
||||
let floID = a[0].replace(/\s/g, ''); //for removing spaces (trailing) if any
|
||||
funds["investments"][floID] = funds["investments"][floID] || {};
|
||||
funds["investments"][floID].amount = parseNumber(a[1])
|
||||
});
|
||||
else if (!cont)
|
||||
switch (d[0].toLowerCase()) {
|
||||
case "start date":
|
||||
funds["start_date"] = new Date(d[1]); break;
|
||||
case "base value":
|
||||
funds["BTC_base"] = parseNumber(d[1].slice(0, -4)); break;
|
||||
case "usd inr rate at start":
|
||||
funds["USD_base"] = parseFloat(d[1]); break;
|
||||
case "duration":
|
||||
funds["duration"] = parsePeriod(d[1]); break;
|
||||
case "management fee":
|
||||
funds["fee"] = parseFloat(d[1]); break;
|
||||
case "tapout availability":
|
||||
let x = d[1].toLowerCase().split("after")
|
||||
funds["tapoutInterval"] = x[1].match(/\d+ [a-z]+/gi).map(y => parsePeriod(y))
|
||||
funds["topoutWindow"] = parsePeriod(x[0]); break;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let floID, details = {};
|
||||
fd.split("|").forEach(d => {
|
||||
d = d.split(': ');
|
||||
switch (d[0].toLowerCase()) {
|
||||
case "investor":
|
||||
floID = d[1]; break;
|
||||
case "end value":
|
||||
details["BTC_net"] = parseNumber(d[1].slice(0, -4)); break;
|
||||
case "date of withdrawal":
|
||||
details["endDate"] = new Date(d[1]); break;
|
||||
case "amount withdrawn":
|
||||
details["amountFinal"] = parseNumber(d[1].match(/\d.+ via/).toString());
|
||||
details["payment_refRef"] = d[1].match(/via .+/).toString().substring(4); break;
|
||||
case "usd inr rate at end":
|
||||
details["USD_net"] = parseFloat(d[1]); break;
|
||||
case "reference":
|
||||
details["refSign"] = d[1]; break;
|
||||
}
|
||||
});
|
||||
if (floID) {
|
||||
funds.investments[floID] = funds.investments[floID] || {};
|
||||
funds.investments[floID].closed = details;
|
||||
}
|
||||
}
|
||||
});
|
||||
return funds;
|
||||
}
|
||||
|
||||
return {
|
||||
productStr,
|
||||
dateAdder,
|
||||
dateFormat,
|
||||
calcNetValue,
|
||||
parse: parse_details,
|
||||
stringify: {
|
||||
main: stringify_main,
|
||||
continue: stringify_continue,
|
||||
end: stringify_end
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
bobsFund.config = {
|
||||
adminID: "FFXy5pJnfzu2fCDLhpUremyXQjGtFpgCDN",
|
||||
application: "BobsFund"
|
||||
}
|
||||
|
||||
function refreshBlockchainData(nodeList = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT num FROM LastTx WHERE floID=?", [bobsFund.config.adminID]).then(result => {
|
||||
let lastTx = result.length ? result[0].num : 0;
|
||||
floBlockchainAPI.readData(bobsFund.config.adminID, {
|
||||
ignoreOld: lastTx,
|
||||
senders: nodeList.concat(bobsFund.config.adminID), //sentOnly: true,
|
||||
tx: true,
|
||||
filter: d => d.startsWith(bobsFund.productStr)
|
||||
}).then(result => {
|
||||
let txQueries = [];
|
||||
result.data.reverse().forEach(d => {
|
||||
let fund = bobsFund.parse(d.data);
|
||||
if (d.senders.has(bobsFund.config.adminID) && !/close:/.test(d.data)) {
|
||||
let fund_id = d.data.match(/continue: [a-z0-9]{64}\|/);
|
||||
if (!fund_id) {
|
||||
fund_id = d.txid;
|
||||
let values = [fund_id, fund.start_date, fund.BTC_base, fund.USD_base, fund.fee, fund.duration];
|
||||
if (fund.tapoutInterval)
|
||||
values.push(fund.topoutWindow, fund.tapoutInterval.join(','));
|
||||
txQueries.push([`INSERT INTO BobsFund(fund_id, begin_date, btc_base, usd_base, fee, duration ${fund.tapoutInterval ? ", tapout_window, tapout_interval" : ""}) VALUE (?) ON DUPLICATE KEY UPDATE fund_id=fund_id`, [values]])
|
||||
} else
|
||||
fund_id = fund_id.pop().match(/[a-z0-9]{64}/).pop();
|
||||
let investments = Object.entries(fund.investments).map(a => [fund_id, a[0], a[1].amount]);
|
||||
txQueries.push(["INSERT INTO BobsFundInvestments(fund_id, floID, amount_in) VALUES ? ON DUPLICATE KEY UPDATE floID=floID", [investments]]);
|
||||
}
|
||||
else {
|
||||
let fund_id = d.data.match(/close: [a-z0-9]{64}\|/);
|
||||
if (fund_id) {
|
||||
fund_id = fund_id.pop().match(/[a-z0-9]{64}/).pop();
|
||||
let closing_details = Object.entries(fund.investments).filter(a => typeof a[1].closed === "object" && a[1].closed.amountFinal).pop(); //only one close-fund will be there in a tx
|
||||
if (closing_details)
|
||||
txQueries.push(["UPDATE BobsFundInvestments SET close_id=?, amount_out=? WHERE fund_id=? AND floID=?",
|
||||
[d.txid, closing_details[1].closed.amountFinal, fund_id, closing_details[0]]])
|
||||
}
|
||||
}
|
||||
});
|
||||
txQueries.push(["INSERT INTO LastTx (floID, num) VALUE (?) ON DUPLICATE KEY UPDATE num=?",
|
||||
[[bobsFund.config.adminID, result.totalTxs], result.totalTxs]])
|
||||
DB.transaction(txQueries)
|
||||
.then(_ => resolve(result.totalTxs))
|
||||
.catch(error => reject(["Bobs-Fund refresh data failed!", error]));
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function closeFund(fund_id, floID, ref) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT r_status, close_id FROM CloseFundTransact WHERE fund_id=? AND floID=?", [fund_id, floID]).then(result => {
|
||||
if (result.length)
|
||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, result[0].r_status == pCode.STATUS_SUCCESS ? `Fund investment already closed (${result[0].close_id})` : `Fund closing already in process`));
|
||||
DB.query("SELECT * FROM BobsFund WHERE fund_id=?", [fund_id]).then(result => {
|
||||
if (!result.length)
|
||||
return reject(INVALID(eCode.NOT_FOUND, 'Fund not found'));
|
||||
let fund = result[0];
|
||||
DB.query("SELECT * FROM BobsFundInvestments WHERE fund_id=? AND floID=?", [fund_id, floID]).then(result => {
|
||||
if (!result.length)
|
||||
return reject(INVALID(eCode.NOT_OWNER, 'User is not an investor of this fund'));
|
||||
let investment = result[0];
|
||||
if (investment.close_id)
|
||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, `Fund investment already closed (${investment.close_id})`));
|
||||
let cur_date = new Date();
|
||||
if (cur_date < bobsFund.dateAdder(fund.begin_date, fund.duration)) {
|
||||
let flag = false;
|
||||
if (fund.tapout_window && fund.tapout_interval) {
|
||||
let tapout_intervals = fund.tapout_interval.split(",");
|
||||
for (let ti of tapout_intervals) {
|
||||
let t_start = bobsFund.dateAdder(fund.begin_date, ti),
|
||||
t_end = bobsFund.dateAdder(t_start, fund.tapout_window);
|
||||
if (t_start < cur_date && cur_date < t_end) {
|
||||
flag = true; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!flag)
|
||||
return reject(INVALID(eCode.INSUFFICIENT_PERIOD, 'Fund still in lock-in period'));
|
||||
}
|
||||
getRate.BTC_USD().then(btc_rate => {
|
||||
getRate.USD_INR().then(usd_rate => {
|
||||
let net_value = bobsFund.calcNetValue(fund.btc_base, btc_rate, fund.usd_base, usd_rate, investment.amount_in, fund.fee)
|
||||
DB.query("INSERT INTO CloseFundTransact(fund_id, floID, amount, end_date, btc_net, usd_net, ref_sign, r_status) VALUE (?)", [[fund_id, floID, net_value, cur_date, btc_rate, usd_rate, ref, pCode.STATUS_PENDING]])
|
||||
.then(result => resolve({ "USD_net": usd_rate, "BTC_net": btc_rate, "amount_out": net_value, "end_date": cur_date }))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function checkFundBalance(prior_time) {
|
||||
return new Promise((resolve, reject) => {
|
||||
prior_time = new Date(prior_time);
|
||||
let cur_date = Date.now();
|
||||
if (isNaN(prior_time) || prior_time.toString() == "Invalid Date")
|
||||
return reject(INVALID(eCode.INVALID_VALUE, `Invalid Date for prior_time`));
|
||||
let sql_query = "SELECT bf.begin_date, bf.btc_base, bf.usd_base, bf.fee, bf.duration, fi.amount_in, cf.amount AS amount_close FROM BobsFund AS bf" +
|
||||
" INNER JOIN BobsFundInvestments AS fi ON bf.fund_id = fi.fund_id" +
|
||||
" LEFT JOIN CloseFundTransact AS cf ON fi.fund_id = cf.fund_id AND fi.floID = cf.floID" +
|
||||
" WHERE fi.close_id IS NULL AND (cf.r_status IS NULL OR cf.r_status NOT IN (?))";
|
||||
DB.query(sql_query, [[pCode.STATUS_SUCCESS, pCode.STATUS_CONFIRMATION]]).then(result => {
|
||||
getRate.BTC_USD().then(btc_rate => {
|
||||
getRate.USD_INR().then(usd_rate => {
|
||||
let pending = { require_amount_cash: 0, n_investment: 0 },
|
||||
ready = { require_amount_cash: 0, n_investment: 0 },
|
||||
upcoming = { require_amount_cash: 0, n_investment: 0 }
|
||||
result.forEach(i => {
|
||||
if (i.amount_close) {
|
||||
pending.require_amount_cash += i.amount_close;
|
||||
pending.n_investment++;
|
||||
} else {
|
||||
let end_date = bobsFund.dateAdder(i.begin_date, i.duration);
|
||||
if (end_date < prior_time) {
|
||||
let net_value = bobsFund.calcNetValue(i.btc_base, btc_rate, i.usd_base, usd_rate, i.amount_in, i.fee);
|
||||
if (end_date > cur_date) {
|
||||
upcoming.require_amount_cash += net_value;
|
||||
upcoming.n_investment++;
|
||||
} else {
|
||||
ready.require_amount_cash += net_value;
|
||||
ready.n_investment++;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
pending.require_amount_cash = global.toStandardDecimal(pending.require_amount_cash);
|
||||
ready.require_amount_cash = global.toStandardDecimal(ready.require_amount_cash);
|
||||
upcoming.require_amount_cash = global.toStandardDecimal(upcoming.require_amount_cash);
|
||||
pending.require_amount_btc = global.toStandardDecimal(pending.require_amount_cash / (btc_rate * usd_rate));
|
||||
ready.require_amount_btc = global.toStandardDecimal(ready.require_amount_cash / (btc_rate * usd_rate));
|
||||
upcoming.require_amount_btc = global.toStandardDecimal(upcoming.require_amount_cash / (btc_rate * usd_rate));
|
||||
Promise.allSettled(sink_chest.list(sink_groups.BOBS_FUND)
|
||||
.map(id => btcOperator.getBalance(btcOperator.convert.legacy2bech(id)))).then(result => {
|
||||
let balance = result.filter(r => r.status === 'fulfilled').reduce((a, bal) => a += bal, 0);
|
||||
resolve({ pending, ready, upcoming, balance });
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
refresh(nodeList) {
|
||||
refreshBlockchainData(nodeList)
|
||||
.then(result => console.debug("Refreshed Bob's Fund data"))
|
||||
.catch(error => console.error(error));
|
||||
},
|
||||
util: bobsFund,
|
||||
checkFundBalance,
|
||||
closeFund
|
||||
}
|
||||
309
src/services/bonds.js
Normal file
309
src/services/bonds.js
Normal file
@ -0,0 +1,309 @@
|
||||
'use strict';
|
||||
|
||||
const DB = require("../database");
|
||||
const { sink_chest, sink_groups } = require("../keys");
|
||||
const eCode = require('../../docs/scripts/floExchangeAPI').errorCode;
|
||||
const pCode = require('../../docs/scripts/floExchangeAPI').processCode;
|
||||
const getRate = require('./conversion').getRate;
|
||||
|
||||
const blockchainBond = (function () {
|
||||
const productStr = "Product: RanchiMall Bitcoin Bond";
|
||||
|
||||
const magnitude = m => {
|
||||
switch (m) {
|
||||
case "thousand": return 1000;
|
||||
case "lakh": case "lakhs": return 100000;
|
||||
case "million": return 1000000;
|
||||
case "crore": case "crores": return 10000000;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
const parseNumber = (str) => {
|
||||
let n = 0,
|
||||
g = 0;
|
||||
str.toLowerCase().replace(/,/g, '').split(" ").forEach(s => {
|
||||
if (!isNaN(s))
|
||||
g = parseFloat(s);
|
||||
else {
|
||||
let m = magnitude(s);
|
||||
if (m !== null) {
|
||||
n += m * g;
|
||||
g = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
return n + g;
|
||||
}
|
||||
const parsePeriod = (str) => {
|
||||
let P = '', n = 0;
|
||||
str.toLowerCase().replace(/,/g, '').split(" ").forEach(s => {
|
||||
if (!isNaN(s))
|
||||
n = parseFloat(s);
|
||||
else switch (s) {
|
||||
case "year(s)": case "year": case "years": P += (n + 'Y'); n = 0; break;
|
||||
case "month(s)": case "month": case "months": P += (n + 'M'); n = 0; break;
|
||||
case "day(s)": case "day": case "days": P += (n + 'D'); n = 0; break;
|
||||
}
|
||||
});
|
||||
return P;
|
||||
}
|
||||
const dateFormat = (date = null) => {
|
||||
let d = (date ? new Date(date) : new Date()).toDateString();
|
||||
return [d.substring(8, 10), d.substring(4, 7), d.substring(11, 15)].join(" ");
|
||||
}
|
||||
const yearDiff = (d1 = null, d2 = null) => {
|
||||
d1 = d1 ? new Date(d1) : new Date();
|
||||
d2 = d2 ? new Date(d2) : new Date();
|
||||
let y = d1.getYear() - d2.getYear(),
|
||||
m = d1.getMonth() - d2.getMonth(),
|
||||
d = d1.getDate() - d2.getDate()
|
||||
return y + m / 12 + d / 365;
|
||||
}
|
||||
|
||||
const dateAdder = function (start_date, duration) {
|
||||
let date = new Date(start_date);
|
||||
let y = parseInt(duration.match(/\d+Y/)),
|
||||
m = parseInt(duration.match(/\d+M/)),
|
||||
d = parseInt(duration.match(/\d+D/));
|
||||
if (!isNaN(y))
|
||||
date.setFullYear(date.getFullYear() + y);
|
||||
if (!isNaN(m))
|
||||
date.setMonth(date.getMonth() + m);
|
||||
if (!isNaN(d))
|
||||
date.setDate(date.getDate() + d);
|
||||
return date;
|
||||
}
|
||||
|
||||
function calcNetValue(BTC_base, BTC_net, startDate, minIpa, maxPeriod, cut, amount, USD_base, USD_net) {
|
||||
let gain, duration, interest, net;
|
||||
gain = (BTC_net - BTC_base) / BTC_base;
|
||||
duration = yearDiff(Math.min(Date.now(), dateAdder(startDate, maxPeriod).getTime()), startDate);
|
||||
interest = Math.max(cut * gain, minIpa * duration);
|
||||
net = amount / USD_base;
|
||||
net += net * interest;
|
||||
return net * USD_net;
|
||||
}
|
||||
|
||||
function stringify_main(BTC_base, start_date, guaranteed_interest, guarantee_period, gain_cut, amount, USD_base, lockin_period, floID) {
|
||||
return [
|
||||
`${productStr}`,
|
||||
`Base value: ${BTC_base} USD`,
|
||||
`Date of bond start: ${dateFormat(start_date)}`,
|
||||
`Guaranteed interest: ${guaranteed_interest}% per annum simple for ${guarantee_period}`,
|
||||
`Bond value: guaranteed interest or ${gain_cut}% of the gains whichever is higher`,
|
||||
`Amount invested: Rs ${amount}`,
|
||||
`USD INR rate at start: ${USD_base}`,
|
||||
`Lockin period: ${lockin_period}`,
|
||||
`FLO ID of Bond Holder: ${floID}`
|
||||
].join("|");
|
||||
}
|
||||
|
||||
function parse_main(data) {
|
||||
//Data (add bond) sent by admin
|
||||
let details = {};
|
||||
data.split("|").forEach(d => {
|
||||
d = d.split(': ');
|
||||
switch (d[0].toLowerCase()) {
|
||||
case "base value":
|
||||
details["BTC_base"] = parseNumber(d[1].slice(0, -4)); break;
|
||||
case "date of bond start":
|
||||
details["startDate"] = new Date(d[1]); break;
|
||||
case "guaranteed interest":
|
||||
details["minIpa"] = parseFloat(d[1].match(/\d+%/)) / 100;
|
||||
details["maxPeriod"] = parsePeriod(d[1].match(/for .+/).toString()); break;
|
||||
case "bond value":
|
||||
details["cut"] = parseFloat(d[1].match(/\d+%/)) / 100; break;
|
||||
case "amount invested":
|
||||
details["amount"] = parseNumber(d[1].substring(3)); break;
|
||||
case "usd inr rate at start":
|
||||
details["USD_base"] = parseFloat(d[1]); break;
|
||||
case "lockin period":
|
||||
details["lockinPeriod"] = parsePeriod(d[1]); break;
|
||||
case "flo id of bond holder":
|
||||
details["floID"] = d[1]; break;
|
||||
}
|
||||
});
|
||||
return details;
|
||||
}
|
||||
|
||||
function stringify_end(bond_id, end_date, BTC_net, USD_net, amount, ref_sign, payment_ref) {
|
||||
return [
|
||||
`${productStr}`,
|
||||
`Bond: ${bond_id}`,
|
||||
`End value: ${BTC_net} USD`,
|
||||
`Date of bond end: ${dateFormat(end_date)}`,
|
||||
`USD INR rate at end: ${USD_net}`,
|
||||
`Amount withdrawn: Rs ${amount} via ${payment_ref}`,
|
||||
`Reference: ${ref_sign}`
|
||||
].join("|");
|
||||
}
|
||||
|
||||
function parse_end(data) {
|
||||
//Data (end bond) send by market nodes
|
||||
let details = {};
|
||||
data.split("|").forEach(d => {
|
||||
d = d.split(': ');
|
||||
switch (d[0].toLowerCase()) {
|
||||
case "bond":
|
||||
details["bondID"] = d[1]; break;
|
||||
case "end value":
|
||||
details["BTC_net"] = parseNumber(d[1].slice(0, -4)); break;
|
||||
case "date of bond end":
|
||||
details["endDate"] = new Date(d[1]); break;
|
||||
case "amount withdrawn":
|
||||
details["amountFinal"] = parseNumber(d[1].match(/\d.+ via/).toString());
|
||||
details["payment_refRef"] = d[1].match(/via .+/).toString().substring(4); break;
|
||||
case "usd inr rate at end":
|
||||
details["USD_net"] = parseFloat(d[1]); break;
|
||||
case "reference":
|
||||
details["refSign"] = d[1]; break;
|
||||
}
|
||||
});
|
||||
return details;
|
||||
}
|
||||
|
||||
return {
|
||||
productStr,
|
||||
dateAdder,
|
||||
dateFormat,
|
||||
calcNetValue,
|
||||
parse: {
|
||||
main: parse_main,
|
||||
end: parse_end
|
||||
},
|
||||
stringify: {
|
||||
main: stringify_main,
|
||||
end: stringify_end
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
blockchainBond.config = {
|
||||
adminID: "FBBstZ2GretgQqDP55yt8iVd4KNZkdvEzH",
|
||||
application: "BlockchainBonds"
|
||||
}
|
||||
|
||||
function refreshBlockchainData(nodeList = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT num FROM LastTx WHERE floID=?", [blockchainBond.config.adminID]).then(result => {
|
||||
let lastTx = result.length ? result[0].num : 0;
|
||||
floBlockchainAPI.readData(blockchainBond.config.adminID, {
|
||||
ignoreOld: lastTx,
|
||||
senders: nodeList.concat(blockchainBond.config.adminID), //sentOnly: true,
|
||||
tx: true,
|
||||
filter: d => d.startsWith(blockchainBond.productStr)
|
||||
}).then(result => {
|
||||
let txQueries = [];
|
||||
result.data.reverse().forEach(d => {
|
||||
let bond = d.senders.has(blockchainBond.config.adminID) ? blockchainBond.parse.main(d.data) : null;
|
||||
if (bond && bond.amount)
|
||||
txQueries.push(["INSERT INTO BlockchainBonds(bond_id, floID, amount_in, begin_date, btc_base, usd_base, gain_cut, min_ipa, max_period, lockin_period) VALUE (?) ON DUPLICATE KEY UPDATE bond_id=bond_id",
|
||||
[[d.txid, bond.floID, bond.amount, bond.startDate, bond.BTC_base, bond.USD_base, bond.cut, bond.minIpa, bond.maxPeriod, bond.lockinPeriod]]]);
|
||||
else {
|
||||
let details = blockchainBond.parse.end(d.data);
|
||||
if (details.bondID && details.amountFinal)
|
||||
txQueries.push(["UPDATE BlockchainBonds SET close_id=?, amount_out=? WHERE bond_id=?",
|
||||
[d.txid, details.amountFinal, details.bondID]]);
|
||||
}
|
||||
});
|
||||
txQueries.push(["INSERT INTO LastTx (floID, num) VALUE (?) ON DUPLICATE KEY UPDATE num=?",
|
||||
[[blockchainBond.config.adminID, result.totalTxs], result.totalTxs]])
|
||||
DB.transaction(txQueries)
|
||||
.then(_ => resolve(result.totalTxs))
|
||||
.catch(error => reject(["Blockchain-bonds refresh data failed!", error]));
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function closeBond(bond_id, floID, ref) {
|
||||
return new Promise((resolve, reject) => {
|
||||
DB.query("SELECT r_status, close_id FROM CloseBondTransact WHERE bond_id=?", [bond_id]).then(result => {
|
||||
if (result.length)
|
||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, result[0].r_status == pCode.STATUS_SUCCESS ? `Bond already closed (${result[0].close_id})` : `Bond closing already in process`));
|
||||
DB.query("SELECT * FROM BlockchainBonds WHERE bond_id=?", [bond_id]).then(result => {
|
||||
if (!result.length)
|
||||
return reject(INVALID(eCode.NOT_FOUND, 'Bond not found'));
|
||||
let bond = result[0];
|
||||
if (bond.floID !== floID)
|
||||
return reject(INVALID(eCode.NOT_OWNER, 'Bond doesnot belong to the user'));
|
||||
if (bond.close_id)
|
||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, `Bond already closed (${bond.close_id})`));
|
||||
if (Date.now() < blockchainBond.dateAdder(bond.begin_date, bond.lockin_period).getTime())
|
||||
return reject(INVALID(eCode.INSUFFICIENT_PERIOD, 'Bond still in lock-in period'));
|
||||
getRate.BTC_USD().then(btc_rate => {
|
||||
getRate.USD_INR().then(usd_rate => {
|
||||
let end_date = new Date(),
|
||||
net_value = blockchainBond.calcNetValue(bond.btc_base, btc_rate, bond.begin_date, bond.min_ipa, bond.max_period, bond.gain_cut, bond.amount_in, bond.usd_base, usd_rate);
|
||||
DB.query("INSERT INTO CloseBondTransact(bond_id, floID, amount, end_date, btc_net, usd_net, ref_sign, r_status) VALUE (?)", [[bond_id, floID, net_value, end_date, btc_rate, usd_rate, ref, pCode.STATUS_PENDING]])
|
||||
.then(result => resolve({ "USD_net": usd_rate, "BTC_net": btc_rate, "amount_out": net_value, "end_date": end_date }))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function checkBondBalance(prior_time) {
|
||||
return new Promise((resolve, reject) => {
|
||||
prior_time = new Date(prior_time);
|
||||
let cur_date = Date.now();
|
||||
if (isNaN(prior_time) || prior_time.toString() == "Invalid Date")
|
||||
return reject(INVALID(eCode.INVALID_VALUE, `Invalid Date for prior_time`));
|
||||
let sql_query = "SELECT bb.*, cb.amount AS amount_close FROM BlockchainBonds AS bb" +
|
||||
" LEFT JOIN CloseBondTransact AS cb ON bb.bond_id = cb.bond_id" +
|
||||
" WHERE bb.close_id IS NULL AND (cb.r_status IS NULL OR cb.r_status NOT IN (?))";
|
||||
DB.query(sql_query, [[pCode.STATUS_SUCCESS, pCode.STATUS_CONFIRMATION]]).then(result => {
|
||||
getRate.BTC_USD().then(btc_rate => {
|
||||
getRate.USD_INR().then(usd_rate => {
|
||||
let pending = { require_amount_cash: 0, n_bond: 0 },
|
||||
ready = { require_amount_cash: 0, n_bond: 0 },
|
||||
upcoming = { require_amount_cash: 0, n_bond: 0 }
|
||||
result.forEach(bond => {
|
||||
if (bond.amount_close) {
|
||||
pending.require_amount_cash += bond.amount_close;
|
||||
pending.n_bond++;
|
||||
} else {
|
||||
let end_date = blockchainBond.dateAdder(bond.begin_date, bond.lockin_period)
|
||||
if (end_date < prior_time) {
|
||||
let net_value = blockchainBond.calcNetValue(bond.btc_base, btc_rate, bond.begin_date, bond.min_ipa, bond.max_period, bond.gain_cut, bond.amount_in, bond.usd_base, usd_rate);
|
||||
if (end_date > cur_date) {
|
||||
upcoming.require_amount_cash += net_value;
|
||||
upcoming.n_bond++;
|
||||
} else {
|
||||
ready.require_amount_cash += net_value;
|
||||
ready.n_bond++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
pending.require_amount_cash = global.toStandardDecimal(pending.require_amount_cash);
|
||||
ready.require_amount_cash = global.toStandardDecimal(ready.require_amount_cash);
|
||||
upcoming.require_amount_cash = global.toStandardDecimal(upcoming.require_amount_cash);
|
||||
pending.require_amount_btc = global.toStandardDecimal(pending.require_amount_cash / (btc_rate * usd_rate));
|
||||
ready.require_amount_btc = global.toStandardDecimal(ready.require_amount_cash / (btc_rate * usd_rate));
|
||||
upcoming.require_amount_btc = global.toStandardDecimal(upcoming.require_amount_cash / (btc_rate * usd_rate));
|
||||
Promise.allSettled(sink_chest.list(sink_groups.BLOCKCHAIN_BONDS)
|
||||
.map(id => btcOperator.getBalance(btcOperator.convert.legacy2bech(id)))).then(result => {
|
||||
let balance = result.filter(r => r.status === 'fulfilled').reduce((a, bal) => a += bal, 0);
|
||||
resolve({ pending, ready, upcoming, balance });
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
refresh(nodeList) {
|
||||
refreshBlockchainData(nodeList)
|
||||
.then(result => console.debug("Refreshed Blockchain-bonds data"))
|
||||
.catch(error => console.error(error));
|
||||
},
|
||||
util: blockchainBond,
|
||||
checkBondBalance,
|
||||
closeBond
|
||||
}
|
||||
283
src/services/conversion.js
Normal file
283
src/services/conversion.js
Normal file
@ -0,0 +1,283 @@
|
||||
'use strict';
|
||||
|
||||
const DB = require("../database");
|
||||
const eCode = require('../../docs/scripts/floExchangeAPI').errorCode;
|
||||
const pCode = require('../../docs/scripts/floExchangeAPI').processCode;
|
||||
|
||||
const {
|
||||
MIN_FUND,
|
||||
TO_FIXED_VALUES,
|
||||
TO_MAX_VALUE,
|
||||
TO_MIN_VALUE,
|
||||
FROM_FIXED_VALUES,
|
||||
FROM_MAX_VALUE,
|
||||
FROM_MIN_VALUE
|
||||
} = require('../_constants')['convert'];
|
||||
|
||||
const allowedConversion = ["BTC"];
|
||||
|
||||
function BTC_INR() {
|
||||
return new Promise((resolve, reject) => {
|
||||
BTC_USD().then(btc_usd => {
|
||||
USD_INR().then(usd_inr => {
|
||||
resolve(btc_usd * usd_inr);
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function BTC_USD() {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch('https://api.coinlore.net/api/ticker/?id=90').then(response => {
|
||||
if (response.ok) {
|
||||
response.json()
|
||||
.then(result => resolve(result[0].price_usd))
|
||||
.catch(error => reject(error));
|
||||
} else
|
||||
reject(response.status);
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
function USD_INR() {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch('https://api.exchangerate-api.com/v4/latest/usd').then(response => {
|
||||
if (response.ok) {
|
||||
response.json()
|
||||
.then(result => resolve(result.rates['INR']))
|
||||
.catch(error => reject(error));
|
||||
} else
|
||||
reject(response.status);
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
function getPoolAvailability(coin) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!allowedConversion.includes(coin))
|
||||
return reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid coin (${coin})`));
|
||||
let q = "SELECT mode, SUM(quantity) AS coin_val, SUM(amount) AS cash_val FROM (" +
|
||||
"(SELECT amount, coin, quantity, mode, r_status FROM DirectConvert) UNION " +
|
||||
"(SELECT amount, coin, quantity, mode, r_status FROM ConvertFund) " +
|
||||
") AS T1 WHERE T1.coin=? AND T1.r_status NOT IN (?) GROUP BY T1.mode";
|
||||
DB.query(q, [coin, [pCode.STATUS_REJECTED]]).then(result => {
|
||||
let coin_net = 0, cash_net = 0;
|
||||
for (let r of result)
|
||||
if (r.mode == pCode.CONVERT_MODE_GET) {
|
||||
coin_net -= r.coin_val;
|
||||
cash_net += r.cash_val;
|
||||
} else if (r.mode == pCode.CONVERT_MODE_PUT) {
|
||||
coin_net += r.coin_val;
|
||||
cash_net -= r.cash_val;
|
||||
}
|
||||
BTC_INR().then(rate => {
|
||||
coin_net = coin_net * rate;
|
||||
let cash_availability = cash_net - coin_net * MIN_FUND,
|
||||
coin_availability = (coin_net - cash_net * MIN_FUND) / rate;
|
||||
if (cash_availability < 0) cash_availability = 0;
|
||||
if (coin_availability < 0) coin_availability = 0;
|
||||
resolve({ cash: cash_availability, coin: coin_availability, rate })
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function checkPoolBalance(coin, req_value, mode) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getPoolAvailability(coin).then(result => {
|
||||
let availability = -1;
|
||||
if (mode == pCode.CONVERT_MODE_GET) {
|
||||
availability = result.coin;
|
||||
req_value = req_value / result.rate; //convert to coin value
|
||||
}
|
||||
else if (mode == pCode.CONVERT_MODE_PUT) {
|
||||
availability = result.cash;
|
||||
req_value = req_value * result.rate; //convert to currency value
|
||||
}
|
||||
if (req_value > availability)
|
||||
reject(INVALID(eCode.INSUFFICIENT_FUND, `Insufficient convert! Availability: ${availability > 0 ? availability : 0}`));
|
||||
else
|
||||
resolve(true);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function getConvertValues() {
|
||||
return new Promise((resolve, reject) => {
|
||||
getPoolAvailability("BTC").then(avail => {
|
||||
let result = {};
|
||||
if (avail.coin > 0) {
|
||||
let coin_availability = global.toStandardDecimal(avail.coin * avail.rate); //convert to currency value
|
||||
if (Array.isArray(TO_FIXED_VALUES) && TO_FIXED_VALUES.length)
|
||||
result[pCode.CONVERT_MODE_GET] = TO_FIXED_VALUES.filter(a => a < coin_availability);
|
||||
else if (!TO_MIN_VALUE || TO_MIN_VALUE <= coin_availability) {
|
||||
result[pCode.CONVERT_MODE_GET] = { min: 0 };
|
||||
result[pCode.CONVERT_MODE_GET].max = (!TO_MAX_VALUE || TO_MAX_VALUE >= coin_availability) ? coin_availability : TO_MAX_VALUE;
|
||||
}
|
||||
} else result[pCode.CONVERT_MODE_GET] = null;
|
||||
if (avail.cash > 0) {
|
||||
let cash_availability = global.toStandardDecimal(avail.cash / avail.rate); //convert to coin value
|
||||
if (Array.isArray(FROM_FIXED_VALUES) && FROM_FIXED_VALUES.length)
|
||||
result[pCode.CONVERT_MODE_PUT] = FROM_FIXED_VALUES.filter(a => a < cash_availability);
|
||||
else if (!FROM_MIN_VALUE || FROM_MIN_VALUE <= cash_availability) {
|
||||
result[pCode.CONVERT_MODE_PUT] = { min: 0 };
|
||||
result[pCode.CONVERT_MODE_PUT].max = (!FROM_MAX_VALUE || FROM_MAX_VALUE >= cash_availability) ? cash_availability : FROM_MAX_VALUE;
|
||||
}
|
||||
} else result[pCode.CONVERT_MODE_PUT] = null;
|
||||
resolve(result)
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function convertToCoin(floID, txid, coin, amount) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!allowedConversion.includes(coin))
|
||||
return reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid coin (${coin})`));
|
||||
else if (typeof amount !== "number" || amount <= 0)
|
||||
return reject(INVALID(eCode.INVALID_NUMBER, `Invalid amount (${amount})`));
|
||||
else if (Array.isArray(TO_FIXED_VALUES) && TO_FIXED_VALUES.length) {
|
||||
if (!TO_FIXED_VALUES.includes(amount))
|
||||
return reject(INVALID(eCode.INVALID_NUMBER, `Invalid amount (${amount})`));
|
||||
} else if (TO_MIN_VALUE && TO_MIN_VALUE > amount || TO_MAX_VALUE && TO_MAX_VALUE < amount)
|
||||
return reject(INVALID(eCode.INVALID_NUMBER, `Invalid amount (${amount})`));
|
||||
DB.query("SELECT r_status FROM DirectConvert WHERE in_txid=? AND floID=? AND mode=?", [txid, floID, pCode.CONVERT_MODE_GET]).then(result => {
|
||||
if (result.length)
|
||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already in process"));
|
||||
checkPoolBalance(coin, amount, pCode.CONVERT_MODE_GET).then(result => {
|
||||
DB.query("INSERT INTO DirectConvert(floID, in_txid, mode, coin, amount, r_status) VALUES (?)", [[floID, txid, pCode.CONVERT_MODE_GET, coin, amount, pCode.STATUS_PENDING]])
|
||||
.then(result => resolve("Conversion request in process"))
|
||||
.catch(error => reject(error));
|
||||
}).catch(error => {
|
||||
if (error instanceof INVALID && error.ecode === eCode.INSUFFICIENT_FUND)
|
||||
DB.query("INSERT INTO DirectConvert(floID, in_txid, mode, coin, amount, r_status) VALUES (?)", [[floID, txid, pCode.CONVERT_MODE_GET, coin, amount, pCode.STATUS_REJECTED]]).then(result => {
|
||||
DB.query("INSERT INTO RefundConvert(floID, in_txid, asset_type, asset, r_status) VALUES (?)", [[floID, txid, pCode.ASSET_TYPE_TOKEN, floGlobals.currency, pCode.STATUS_PENDING]])
|
||||
.then(_ => null).catch(error => console.error(error));
|
||||
}).catch(error => console.error(error))
|
||||
reject(error);
|
||||
})
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
|
||||
function convertFromCoin(floID, txid, tx_hex, coin, quantity) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!allowedConversion.includes(coin))
|
||||
return reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid coin (${coin})`));
|
||||
else if (typeof quantity !== "number" || quantity <= 0)
|
||||
return reject(INVALID(eCode.INVALID_NUMBER, `Invalid quantity (${quantity})`));
|
||||
else if (Array.isArray(FROM_FIXED_VALUES) && FROM_FIXED_VALUES.length) {
|
||||
if (!FROM_FIXED_VALUES.includes(quantity))
|
||||
return reject(INVALID(eCode.INVALID_NUMBER, `Invalid quantity (${quantity})`));
|
||||
} else if (FROM_MIN_VALUE && FROM_MIN_VALUE > quantity || FROM_MAX_VALUE && FROM_MAX_VALUE < quantity)
|
||||
return reject(INVALID(eCode.INVALID_NUMBER, `Invalid quantity (${quantity})`));
|
||||
else if (btcOperator.transactionID(tx_hex) !== txid)
|
||||
return reject(INVALID(eCode.INVALID_TX_ID, `txid ${txid} doesnt match the tx-hex`));
|
||||
DB.query("SELECT r_status FROM DirectConvert WHERE in_txid=? AND floID=? AND mode=?", [txid, floID, pCode.CONVERT_MODE_PUT]).then(result => {
|
||||
if (result.length)
|
||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already in process"));
|
||||
checkPoolBalance(coin, quantity, pCode.CONVERT_MODE_PUT).then(result => {
|
||||
btcOperator.broadcastTx(tx_hex).then(b_txid => {
|
||||
if (b_txid !== txid)
|
||||
console.warn("broadcast TX-ID is not same as calculated TX-ID");
|
||||
DB.query("INSERT INTO DirectConvert(floID, in_txid, mode, coin, quantity, r_status) VALUES (?)", [[floID, b_txid, pCode.CONVERT_MODE_PUT, coin, quantity, pCode.STATUS_PENDING]])
|
||||
.then(result => resolve("Conversion request in process"))
|
||||
.catch(error => reject(error));
|
||||
}).catch(error => {
|
||||
if (error === null)
|
||||
reject(INVALID(eCode.INVALID_TX_ID, `Invalid transaction hex`));
|
||||
else
|
||||
reject(error);
|
||||
})
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function depositCurrencyFund(floID, txid, coin) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (floID !== floGlobals.adminID)
|
||||
return reject(INVALID(eCode.ACCESS_DENIED, 'Access Denied'));
|
||||
else if (!allowedConversion.includes(coin))
|
||||
return reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid coin (${coin})`));
|
||||
DB.query("SELECT r_status FROM ConvertFund WHERE txid=? AND mode=?", [txid, pCode.CONVERT_MODE_GET]).then(result => {
|
||||
if (result.length)
|
||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already in process"));
|
||||
DB.query("INSERT INTO ConvertFund(txid, mode, coin, r_status) VALUES (?)", [[txid, pCode.CONVERT_MODE_GET, coin, pCode.STATUS_PROCESSING]])
|
||||
.then(result => resolve("Deposit currency fund in process"))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function depositCoinFund(floID, txid, coin) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (floID !== floGlobals.adminID)
|
||||
return reject(INVALID(eCode.ACCESS_DENIED, 'Access Denied'));
|
||||
else if (!allowedConversion.includes(coin))
|
||||
return reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid coin (${coin})`));
|
||||
DB.query("SELECT r_status FROM ConvertFund WHERE txid=? AND mode=?", [txid, pCode.CONVERT_MODE_PUT]).then(result => {
|
||||
if (result.length)
|
||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already in process"));
|
||||
DB.query("INSERT INTO ConvertFund(txid, mode, coin, r_status) VALUES (?)", [[txid, pCode.CONVERT_MODE_PUT, coin, pCode.STATUS_PROCESSING]])
|
||||
.then(result => resolve("Deposit coin fund in process"))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function withdrawCurrencyFund(floID, coin, amount) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (floID !== floGlobals.adminID)
|
||||
return reject(INVALID(eCode.ACCESS_DENIED, 'Access Denied'));
|
||||
else if (!allowedConversion.includes(coin))
|
||||
return reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid coin (${coin})`));
|
||||
DB.query("SELECT SUM(amount) AS deposit_amount FROM ConvertFund WHERE mode=? AND r_status=?", [pCode.CONVERT_MODE_GET, pCode.STATUS_SUCCESS]).then(r1 => {
|
||||
DB.query("SELECT SUM(amount) AS withdraw_amount FROM ConvertFund WHERE mode=? AND r_status IN (?)", [pCode.CONVERT_MODE_PUT, [pCode.STATUS_SUCCESS, pCode.STATUS_PENDING, pCode.STATUS_CONFIRMATION]]).then(r2 => {
|
||||
let available_amount = (r1[0].deposit_amount || 0) - (r2[0].withdraw_amount || 0);
|
||||
if (available_amount < amount)
|
||||
return reject(INVALID(eCode.INSUFFICIENT_BALANCE, "Insufficient convert-fund deposits to withdraw"));
|
||||
DB.query("INSERT INTO ConvertFund(mode, coin, amount, r_status) VALUES (?)", [[pCode.CONVERT_MODE_PUT, coin, amount, pCode.STATUS_PENDING]])
|
||||
.then(result => resolve("Withdraw currency fund in process"))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function withdrawCoinFund(floID, coin, quantity) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (floID !== floGlobals.adminID)
|
||||
return reject(INVALID(eCode.ACCESS_DENIED, 'Access Denied'));
|
||||
else if (!allowedConversion.includes(coin))
|
||||
return reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid coin (${coin})`));
|
||||
DB.query("SELECT SUM(quantity) AS deposit_quantity FROM ConvertFund WHERE mode=? AND r_status=?", [pCode.CONVERT_MODE_PUT, pCode.STATUS_SUCCESS]).then(r1 => {
|
||||
DB.query("SELECT SUM(quantity) AS withdraw_quantity FROM ConvertFund WHERE mode=? AND r_status IN (?)", [pCode.CONVERT_MODE_GET, [pCode.STATUS_SUCCESS, pCode.STATUS_PENDING]]).then(r2 => {
|
||||
let available_quantity = (r1[0].deposit_quantity || 0) - (r2[0].withdraw_quantity || 0);
|
||||
if (available_quantity < quantity)
|
||||
return reject(INVALID(eCode.INSUFFICIENT_BALANCE, "Insufficient convert-fund deposits to withdraw"));
|
||||
DB.query("INSERT INTO ConvertFund(mode, coin, quantity, r_status) VALUES (?)", [[pCode.CONVERT_MODE_GET, coin, quantity, pCode.STATUS_PENDING]])
|
||||
.then(result => resolve("Withdraw currency fund in process"))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getRate: {
|
||||
BTC_USD,
|
||||
USD_INR,
|
||||
BTC_INR
|
||||
},
|
||||
getConvertValues,
|
||||
convertToCoin,
|
||||
convertFromCoin,
|
||||
depositFund: {
|
||||
coin: depositCoinFund,
|
||||
currency: depositCurrencyFund
|
||||
},
|
||||
withdrawFund: {
|
||||
coin: withdrawCoinFund,
|
||||
currency: withdrawCurrencyFund
|
||||
}
|
||||
}
|
||||
@ -2,16 +2,6 @@
|
||||
//fetch for node js (used in floBlockchainAPI.js)
|
||||
global.fetch = require("node-fetch");
|
||||
|
||||
global.convertDateToString = function(timestamp) {
|
||||
let date = new Date(timestamp);
|
||||
return date.getFullYear() + '-' +
|
||||
('00' + (date.getMonth() + 1)).slice(-2) + '-' +
|
||||
('00' + date.getDate()).slice(-2) + ' ' +
|
||||
('00' + date.getHours()).slice(-2) + ':' +
|
||||
('00' + date.getMinutes()).slice(-2) + ':' +
|
||||
('00' + date.getSeconds()).slice(-2);
|
||||
}
|
||||
|
||||
//Set browser paramaters from param.json (or param-default.json)
|
||||
var param;
|
||||
try {
|
||||
@ -23,6 +13,11 @@ try {
|
||||
global[p] = param[p];
|
||||
}
|
||||
|
||||
global.toStandardDecimal = num => parseFloat((parseInt(num * 1e8) * 1e-8).toFixed(8))
|
||||
|
||||
if (!process.argv.includes("--debug"))
|
||||
global.console.debug = () => null;
|
||||
|
||||
/*
|
||||
//Trace the debug logs in node js
|
||||
var debug = console.debug;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user