diff --git a/.env.sample b/.env.sample index 8a0d27c..46daac5 100644 --- a/.env.sample +++ b/.env.sample @@ -1,4 +1,39 @@ +#____API Section + +API_ENABLED=true +API_PORT=3000 + +#_____NODE SERVER Section + +#mainnet +# XRPL_CLIENT=wss://xrplcluster.com +# XRPL_CLIENT=wss://s2.ripple.com +# XAHAU_CLIENT=wss://xahau.network +# XRPL_LEDGER_INDEX=83525625 +# XAHAU_LEDGER_INDEX=2 + +#testnet XRPL_CLIENT=wss://s.altnet.rippletest.net:51233/ XAHAU_CLIENT=wss://xahau-test.net XRPL_LEDGER_INDEX=41790000 -XAHAU_LEDGER_INDEX=7159000 \ No newline at end of file +XAHAU_LEDGER_INDEX=2082589 + + +#___DATABASE Section + +DB_TYPE=sqlite +#DB_TYPE=mariadb + +#testnet +DB_HOST=127.1.1.1 +DB_PORT=25500 +DB_USER=username +DB_PASS=password +DB_NAME=b2m_testnet + +#mainnet +# DB_HOST=127.1.1.1 +# DB_PORT=25500 +# DB_USER=username +# DB_PASS=password +# DB_NAME=b2m_mainnet \ No newline at end of file diff --git a/.gitignore b/.gitignore index 008bc0b..3a3a1bd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,12 @@ db/:account: db/:record: +db/_account.sqlite3 +db/_record.sqlite3 log/ERR.txt log/INF.txt -node_modules/ +log/WRN.txt +node_modules/ +tester.js +.vscode +.npm .env \ No newline at end of file diff --git a/README.md b/README.md index d7681d7..88c5e69 100755 --- a/README.md +++ b/README.md @@ -1,8 +1,12 @@ # Burn2Mint Indexer -Index through the XRPL & Xahau for B2M traffic via rippled & xahaud websocket API. This indexer is serverless and is meant to be hosted 24/7. +Will Index through the XRPL & Xahau ledgers for B2M traffic, URITokens Hooks via configured websocket API. +after indexing through ledgers, it will listen for ongoing transactions. +This indexer is serverless and is meant to be hosted 24/7. + +> This indexer supports indexing/logging of XRP burns, XAH Import Mints, URITokens, and basic HookSet/Invoke log. +> Its mainly been tested with MariaDB due to the speed of that DB, sqlite works ok if theres not much action due to "SQL_BUSY" errors -> This indexer is only supports XRP-to-XAH B2M's. ### Installation @@ -10,6 +14,7 @@ Install nodejs dependencies: ``` npm install ``` +this installs and sets up all the packages thats are listed in packages.json ### Set up .env file @@ -18,26 +23,63 @@ To copy the default (sample) `.env` file: cp -r .env.sample .env ``` -**NOTE**: For testing purposes, you can put any arbitrary ledger index to start from. +**NOTE**: For testing purposes, you can put any arbitrary ledger index to start from, the basic setting should get you going. + +`.env` file is use to set up the envirment varibles +so it can enable the API, set the API_PORT, choose the Database type etc +the sample .env has been split into section to make it a little clearer -`.env` structure: -``` -XRPL_CLIENT=XRPL node wss address -XAHAU_CLIENT=Xahau node wss address -XRPL_LEDGER_INDEX=The ledger containing the first Burn tx -XAHAU_LEDGER_INDEX=The ledger in which XahauGenesis went live ``` +#API section +API_ENABLED=true - to enable or discable the API server +API_PORT=3000 - the API port the server will listen on + +#node server section +XRPL_CLIENT=XRPL - node wss address +XAHAU_CLIENT=Xahau - node wss address +XRPL_LEDGER_INDEX= - The ledger containing the first Burn tx +XAHAU_LEDGER_INDEX= - The ledger in which XahauGenesis went live + +##database section +DB_TYPE=sqlite3 - the database type it will use, currently either sqlite, or mariadb + +#testnet +DB_HOST=127.1.1.1 - ip/host of the maria db +DB_PORT=25500 - port the db is using +DB_USER=esername - username of the db +DB_PASS=password - password of the db +DB_NAME=b2m_testnet - and the database name +``` ## Run B2M-indexer -Location: `/src` +all the main programs are in file Location: `/src` + +but the coding is setup so that the "working directory" is the root location for eg To run `B2M-indexer`: ``` -node main.js +node src/main.js ``` -To check B2M stats, run: +To run and check local B2M stats, run: +``` +node src/stats.js ``` -node stats.js -``` \ No newline at end of file + +## API Server +the API server has the same info than the stat.jjs has. +the root, for exmple, if this was your PC's IP, and you kept the default PORT; +>http://192.168.0.1:3000 + +would give the stats + +`/all` gives a list of all accounts and details. + +`/account/
` allows you to search a single account. + +`/history/daily` gives you info split by day, or specify the date `/history/daily/` + +`/history/monthly` gives you info split by month, or specify the month `/history/monthly/` + +`/uri/` allows you to search for a URIToken via its URITokenID diff --git a/db/manager.js b/db/manager.js index 9374738..6a66f86 100755 --- a/db/manager.js +++ b/db/manager.js @@ -1,197 +1,874 @@ -const sqlite3 = require('sqlite3').verbose(); +const { Log } = require("../log/logger.js"); +const dotenv = require("dotenv").config({path:"./.env"}); +const dbType = process.env.DB_TYPE; +BigInt.prototype.toJSON = function() { return this.toString(); } +var recordFlag = {} +var drecordFlag = {} +var mrecordFlag = {} -/** -* Get an account's B2M record. -* @param {string} address - Account address -*/ -async function GetAccountRecord(address) { - const dbAccount = new sqlite3.Database('../db/:account:'); // General Account Burn-To-Mint records - return new Promise((resolve, reject) => { - dbAccount.get("SELECT * FROM account WHERE address = ?", [address], function(err, record) { - if(err) reject(err); - dbAccount.close(); - resolve(record); - }); - }) -} +// Database base interface +class Database { -/** -* Retrieve *all* accounts that has performed B2M. -*/ -async function GetAllAccountRecord() { - const dbAccount = new sqlite3.Database('../db/:account:'); // General Account Burn-To-Mint records - return new Promise((resolve, reject) => { - dbAccount.all('SELECT * FROM account', [], (err, rows) => { - if(err) reject(err); - dbAccount.close(); - resolve(rows); - }); - }) + /**Get an account's B2M record. + * @param {string} address - Account address + */ + getAccountRecord() { + throw new Error('getAccountRecord needs database type from .env file'); + } + + /**Retrieve *all* accounts that has performed B2M. + */ + GetAllAccountRecord() { + throw new Error('GetAllAccountRecord needs database type from .env file'); + } + + /**Get all the date's B2M performance. + * @param {*} record_name - The table's name: "daily" or "monthly" + */ + GetAllHistoryRecord() { + throw new Error('GetAllHistoryRecord needs database type from .env file'); + } + + /**Get a specific date's B2M performance. + * @param {string} record_name - The table's name: "daily" or "monthly" + * @param {string} date - The date - YYYY-MM-DD + * @returns + */ + GetHistoryRecord() { + throw new Error('GetHistoryRecord needs database type from .env file'); + } + + /**Internal DB book keeping - nothing much... + * @param {string} key - The variable's key + */ + GetMiscRecord() { + throw new Error('GetMiscRecord needs database type from .env file'); + } + + /**Generate a DB record for an account. + * @param {string} address + * @param {number} burnt_amount + * @param {number} minted_amount + * @param {number} burn_tx_count + * @param {number} mint_tx_count + */ + GenerateAccountRecord() { + throw new Error('GenerateAccountRecord needs database type from .env file'); + } + + /**Generate a DB record for a specific & unique date. + * @param {string} record_name - The table's name: "daily" or "monthly" + * @param {*} date - The date: YYYY-MM-DD + * @param {number} burnt_amount + * @param {number} minted_amount + * @param {number} burn_tx_count + * @param {number} mint_tx_count + * @param {number} newly_funded_account + */ + GenerateHistoryRecord() { + throw new Error('GenerateHistoryRecord needs database type from .env file'); + } + + /**Generate a misc record (internal DB) + * @param {text} key + * @param {number} value + */ + GenerateMiscRecord() { + throw new Error('GenerateMiscRecord needs database type from .env file'); + } + + /**Update an account's record. + * @param {text} address - Account address + * @param {text} key1 - burn/mint_amount + * @param {number} value1 - XRP amount + * @param {text} key2 - burn/mint_count + * @param {number} value2 - tx count + */ + UpdateAccountRecord() { + throw new Error('UpdateAccountRecord needs database type from .env file'); + } + + /**Update date record. + * @param {text} record_name - Table name + * @param {date} date - Date (YYYY-MM-DD) + * @param {text} key1 - burn/mint_amount + * @param {number} value1 - XRP amount + * @param {text} key2 - burn/mint_count + * @param {number} value2 - tx count + * @param {text} key3 - newly_funded_account (optional) + * @param {number} value3 - Number of accounts (optional) + */ + UpdateHistoryRecord() { + throw new Error('UpdateHistoryRecord needs database type from .env file'); + } + + /**Update misc record (internal DB) + * @param {text} key + * @param {number} value + */ + UpdateMiscRecord() { + throw new Error('UpdateMiscRecord needs database type from .env file'); + } + + /**Update Used to Add URItokens to the uritokens dbb + * @param {text} address - account address + * @param { [text,text] } uritokensToAdd - array of token[s] to Add + * @param {text} hash - tx hash + */ + async URITokensAdd(address, uritokensToAdd) { + throw new Error('URITokensAdd needs database type from .env file'); + } + + /**Update Used to Remove URItokens to the uritokens dbb + * @param {text} address - account address + * @param { [text,text] } uritokensToAdd - array of token[s] to Remmove + * @param {text} hash - tx hash + */ + async URITokensRemove(address, uritokensToAdd) { + throw new Error('URITokensRemove needs database type from .env file'); + } } -/** -* Get a specific date's B2M performance. -* @param {string} record_name - The table's name: "daily" or "monthly" -* @param {string} date - The date - YYYY-MM-DD -* @returns -*/ -async function GetHistoryRecord(record_name, date) { - const dbRecord = new sqlite3.Database('../db/:record:'); // Analytical records - return new Promise((resolve, reject) => { - dbRecord.get(`SELECT * FROM ${record_name} WHERE date = ?`, [date], function(err, record) { - if(err) reject(err); - dbRecord.close(); - resolve(record); - }); - }) +//#################################################################################################################### + +// SQLite implementation +class SQLiteDatabase extends Database { + + async GetAccountRecord(address) { + const dbAccount = new sqlite3.Database('./db/_account.sqlite3'); // General Account Burn-To-Mint records + return new Promise((resolve, reject) => { + dbAccount.get("SELECT * FROM account WHERE address = ?", [address], function(err, record) { + if (!record || record.length === 0) { //checking against null + resolve({}); + dbAccount.close(); + return; + } + if (err) { + dbAccount.close(); + reject(err); + } else { + resolve(record); + dbAccount.close(); + } + }); + }) + } + + async GetAllAccountRecord() { + const dbAccount = new sqlite3.Database('./db/_account.sqlite3'); // General Account Burn-To-Mint records + return new Promise((resolve, reject) => { + dbAccount.all('SELECT * FROM account', [], (err, rows) => { + if (err) { + dbAccount.close(); + reject(err); + } else { + dbAccount.close(); + resolve(rows); + } + }); + }) + } + + async GetAllHistoryRecord(record_name) { + const dbRecord = new sqlite3.Database('./db/_record.sqlite3'); // Analytical records + return new Promise((resolve, reject) => { + dbRecord.all(`SELECT * FROM ${record_name}`, [], function(err, record) { + if (err) { + dbRecord.close(); + reject(err); + } else { + dbRecord.close(); + resolve(record); + } + }); + }) + } + + async GetHistoryRecord(record_name, date) { + const dbRecord = new sqlite3.Database('./db/_record.sqlite3'); // Analytical records + return new Promise((resolve, reject) => { + dbRecord.get(`SELECT * FROM ${record_name} WHERE date = ?`, [date], function(err, record) { + if (err) { + dbRecord.close(); + reject(err); + } else { + dbRecord.close(); + resolve(record); + } + }); + }) + } + + async GetMiscRecord(key) { + const dbRecord = new sqlite3.Database('./db/_record.sqlite3'); // Analytical records + return new Promise((resolve, reject) => { + dbRecord.get(`SELECT * FROM misc WHERE key = ?`, [key], function(err, record) { + if (err) { + dbRecord.close(); + reject(err); + } else { + dbRecord.close(); + resolve(record); + } + }); + }) + } + +// END OF ASYNC FUNCTIONS + + async GenerateAccountRecord(address, burnt_amount, minted_amount, burn_tx_count, mint_tx_count, uritoken_mint_count, uritoken_burn_count, uritoken_buy_count, uritoken_sell_count, hook_count, hookinvoke_count) { + const dbAccount = new sqlite3.Database('./db/_account.sqlite3'); + dbAccount.serialize(function() { + var insertAccountRecord = dbAccount.prepare("INSERT INTO account VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + insertAccountRecord.run(address, burnt_amount, minted_amount, burn_tx_count, mint_tx_count, uritoken_mint_count, uritoken_burn_count, uritoken_buy_count, uritoken_sell_count, hook_count, hookinvoke_count); + insertAccountRecord.finalize(); + }); + dbAccount.close(); + } + + async GenerateHistoryRecord(record_name, date, burnt_amount, minted_amount, burn_tx_count, mint_tx_count, newly_funded_account, uritoken_mint_count, uritoken_burn_count, uritoken_buy_count, uritoken_sell_count, hook_count, hookinvoke_count) { + const dbRecord = new sqlite3.Database('./db/_record.sqlite3'); + dbRecord.serialize(function() { + var insertHistoryRecord = dbRecord.prepare(`INSERT INTO ${record_name} VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`); + insertHistoryRecord.run(date, burnt_amount, minted_amount, burn_tx_count, mint_tx_count, newly_funded_account, uritoken_mint_count, uritoken_burn_count, uritoken_buy_count, uritoken_sell_count, hook_count, hookinvoke_count); + insertHistoryRecord.finalize(); + }); + dbRecord.close(); + } + + async GenerateMiscRecord(key, value) { + const dbRecord = new sqlite3.Database('./db/_record.sqlite3'); + dbRecord.serialize(function() { + var insertHistoryRecord = dbRecord.prepare(`INSERT INTO misc VALUES (?, ?)`); + insertHistoryRecord.run(key, value); + insertHistoryRecord.finalize(); + }); + dbRecord.close(); + } + + + async UpdateAccountRecord(address, key1, value1, key2, value2) { + const dbAccount = new sqlite3.Database('./db/_account.sqlite3'); + dbAccount.serialize(function() { + dbAccount.run(`UPDATE account SET ${key1} = ?, ${key2} = ? WHERE address = ?`, [value1, value2, address]) + }); + dbAccount.close(); + } + + async UpdateHistoryRecord(record_name, date, key1, value1, key2, value2, key3, value3) { + const dbRecord = new sqlite3.Database('./db/_record.sqlite3'); + if (key3 === undefined || value3 === undefined) { + var command = `UPDATE ${record_name} SET ${key1} = ?, ${key2} = ? WHERE date = ?`; + var values = [value1, value2, date]; + } else { + var command = `UPDATE ${record_name} SET ${key1} = ?, ${key2} = ?, ${key3} = ? WHERE date = ?`; + var values = [value1, value2, value3, date]; + } + dbRecord.serialize(function() { + dbRecord.run(command, values) + }); + dbRecord.close(); + } + + async UpdateMiscRecord(key, value) { + const dbRecord = new sqlite3.Database('./db/_record.sqlite3'); + dbRecord.serialize(function() { + dbRecord.run(`UPDATE misc SET value = ? WHERE key = ?`, [value, key]) + }); + dbRecord.close(); + } } -/** -* Get all the date's B2M performance. -* @param {*} record_name - The table's name: "daily" or "monthly" -*/ -async function GetAllHistoryRecord(record_name) { - const dbRecord = new sqlite3.Database('../db/:record:'); // Analytical records - return new Promise((resolve, reject) => { - dbRecord.all(`SELECT * FROM ${record_name}`, [], function(err, record) { - if(err) reject(err); - dbRecord.close(); - resolve(record); - }); - }) + + +//#################################################################################################################### + +// MariaDB implementation +class MariaDBDatabase extends Database { + + async GetAccountRecord(address) { + return new Promise((resolve, reject) => { + pool.getConnection() + .then(conn => { + conn.query("SELECT * FROM account WHERE address = ?", [address]) + .then((accountRows) => { + if (!accountRows || accountRows.length === 0) { //checking against null + resolve({}); + conn.end(); + return; + } + conn.query("SELECT * FROM uritokens WHERE address = ?", [address]) + .then((uriTokenRows) => { + if (!uriTokenRows) { + uriTokenRows = []; + } + var combinedData = Object.assign(accountRows[0], uriTokenRows[0]); + resolve(combinedData); + conn.end(); + }) + .catch(err => { + conn.end(); + reject(err); + }); + }) + .catch(err => { + conn.end(); + reject(err); + }); + }).catch(err => { + reject(err); + }); + }); + } + + async GetAllAccountRecord() { + return new Promise((resolve, reject) => { + pool.getConnection() + .then(conn => { + conn.query("SELECT * FROM account") + .then((rows) => { + resolve(rows); + conn.end(); + }) + .catch(err => { + conn.end(); + reject(err); + }); + }).catch(err => { + reject(err); + }); + }); + } + + async GetAllHistoryRecord(record_name) { + return new Promise((resolve, reject) => { + pool.getConnection() + .then(conn => { + conn.query(`SELECT * FROM ${record_name}`) + .then((rows) => { + resolve(rows); + conn.end(); + }) + .catch(err => { + conn.end(); + reject(err); + }); + }).catch(err => { + reject(err); + }); + }); + } + + async GetHistoryRecord(record_name, date) { + return new Promise((resolve, reject) => { + pool.getConnection() + .then(conn => { + conn.query(`SELECT * FROM ${record_name} WHERE date = ?`, [date]) + .then((rows) => { + resolve(rows[0]); + conn.end(); + }) + .catch(err => { + conn.end(); + reject(err); + }); + }).catch(err => { + reject(err); + }); + }); + } + + async GetMiscRecord(key) { + return new Promise((resolve, reject) => { + pool.getConnection() + .then(conn => { + conn.query(`SELECT * FROM misc WHERE \`key\` = ?`, [key]) + .then((rows) => { + resolve(rows[0] || undefined); // Return the first record or null if no records + conn.end(); + }) + .catch(err => { + conn.end(); + reject(err); + }); + }).catch(err => { + reject(err); + }); + }); + } + + async GetURIRecord(uri) { + return new Promise((resolve, reject) => { + pool.getConnection() + .then(conn => { + conn.query("SELECT * FROM uritokens WHERE uri = ?", [uri]) + .then((rows) => { + resolve(rows[0]); + conn.end(); + }) + .catch(err => { + conn.end(); + reject(err); + }); + }).catch(err => { + reject(err); + }); + }); + } + + async GenerateAccountRecord(address, burnt_amount, minted_amount, burn_tx_count, mint_tx_count, uritoken_mint_count, uritoken_burn_count, uritoken_buy_count, uritoken_sell_count, hook_count, hookinvoke_count) { + return new Promise((resolve, reject) => { + pool.getConnection() + .then(conn => { + conn.query("INSERT INTO account VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [address, burnt_amount, minted_amount, burn_tx_count, mint_tx_count, uritoken_mint_count, uritoken_burn_count, uritoken_buy_count, uritoken_sell_count, hook_count, hookinvoke_count]) + .then(() => { + resolve(); + conn.end(); + }) + .catch(err => { + conn.end(); + reject(err); + }); + }).catch(err => { + reject(err); + }); + }); + } + + async GenerateHistoryRecord(record_name, date, burnt_amount, minted_amount, burn_tx_count, mint_tx_count, newly_funded_account, uritoken_mint_count, uritoken_burn_count, uritoken_buy_count, uritoken_sell_count, hook_count, hookinvoke_count) { + return new Promise((resolve, reject) => { + pool.getConnection() + .then(conn => { + conn.query(`INSERT INTO ${record_name} VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [date, burnt_amount, minted_amount, burn_tx_count, mint_tx_count, newly_funded_account, uritoken_mint_count, uritoken_burn_count, uritoken_buy_count, uritoken_sell_count, hook_count, hookinvoke_count]) + .then(() => { + resolve(); + conn.end(); + }) + .catch(err => { + conn.end(); + reject(err); + }); + }).catch(err => { + reject(err); + }); + }); + } + + async GenerateMiscRecord(key, value) { + return new Promise((resolve, reject) => { + pool.getConnection() + .then(conn => { + conn.query(`INSERT INTO misc VALUES (?, ?)`, [key, value]) + .then(() => { + resolve(); + conn.end(); + }) + .catch(err => { + conn.end(); + reject(err); + }); + }).catch(err => { + reject(err); + }); + }); + } + + async UpdateAccountRecord(address, key1, value1, key2, value2, key3 , value3, key4, value4) { + return new Promise((resolve, reject) => { + pool.getConnection() + .then(conn => { + let query = `UPDATE account SET ${key1} = ?, ${key2} = ? WHERE address = ?`; + let values = [value1, value2, address]; + if(key3 !== undefined && value3 !== undefined) { + query = `UPDATE account SET ${key1} = ?, ${key2} = ?, ${key3} = ? WHERE address = ?`; + values = [value1, value2, value3, address]; + } + if(key4 !== undefined && value4 !== undefined) { + query = `UPDATE account SET ${key1} = ?, ${key2} = ?, ${key3} = ?, ${key4} = ? WHERE address = ?`; + values = [value1, value2, value3, value4, address]; + } + conn.query(query, values) + .then(() => { + resolve(); + conn.end(); + }) + .catch(err => { + conn.end(); + reject(err); + }); + }).catch(err => { + reject(err); + }); + }); + } + + async UpdateHistoryRecord(record_name, date, key1, value1, key2, value2, key3, value3, key4, value4) { + return new Promise((resolve, reject) => { + pool.getConnection() + .then(conn => { + let query = `UPDATE ${record_name} SET ${key1} = ?, ${key2} = ? WHERE date = ?`; + let values = [value1, value2, date]; + if(key3 !== undefined && value3 !== undefined) { + query = `UPDATE ${record_name} SET ${key1} = ?, ${key2} = ?, ${key3} = ? WHERE date = ?`; + values = [value1, value2, value3, date]; + } + if(key4 !== undefined && value4 !== undefined) { + query = `UPDATE ${record_name} SET ${key1} = ?, ${key2} = ?, ${key3} = ?, ${key4} = ? WHERE date = ?`; + values = [value1, value2, value3, value4, date]; + } + conn.query(query, values) + .then(() => { + resolve(); + conn.end(); + }) + .catch(err => { + conn.end(); + reject(err); + }); + }).catch(err => { + reject(err); + }); + }); + } + + async UpdateMiscRecord(key, value) { + return new Promise((resolve, reject) => { + pool.getConnection() + .then(conn => { + conn.query(`UPDATE misc SET value = ? WHERE \`key\` = ?`, [value, key]) + .then(() => { + resolve(); + conn.end(); + }) + .catch(err => { + conn.end(); + reject(err); + }); + }).catch(err => { + reject(err); + }); + }); + } + + async URITokensAdd(address, uritokenidToAdd, uri, txHash) { + //Log("INF", `within addURIToken -> [0] ${address} and [1] ${uritokenidToAdd}`); + return new Promise((resolve, reject) => { + pool.getConnection() + .then(conn => { + // Array to Insert new URIs into the objects table + let promises = uritokenidToAdd.map(uritokenid => { + let query = `INSERT INTO uritokens (address, uritokenid, uri, txHash) VALUES (?, ?, ?, ?)`; + return conn.query(query, [address, uritokenid, uri, txHash]); + }); + return Promise.all(promises) + .then(() => { + resolve(); + conn.end(); + }) + .catch(err => { + conn.end(); + reject(err); + }); + }).catch(err => { + reject(err); + }); + }); + } + + async URITokensRemove(address, uritokenidToRemove) { + return new Promise((resolve, reject) => { + pool.getConnection() + .then(conn => { + // Array to Remove specified URIs from the objects table + let promises = uritokenidToRemove.map(uritokenid => { + let query = `DELETE FROM uritokens WHERE address = ? AND uritokenid = ?`; + return conn.query(query, [address, uritokenid]); + }); + return Promise.all(promises) + .then(() => { + resolve(); + conn.end(); + }) + .catch(err => { + conn.end(); + reject(err); + }); + }).catch(err => { + reject(err); + }); + }); + } + + async RecordHookSet(address, HookNamespaceAdd, HookSetTxnID, HookOn, txHash) { + //Log("INF", `within addURIToken -> [0] ${address} and [1] ${uritokenidToAdd}`); + return new Promise((resolve, reject) => { + pool.getConnection() + .then(conn => { + // Array to Insert new URIs into the objects table + let promises = HookNamespaceAdd.map(HookNamespace => { + let query = `INSERT INTO hooks (address, HookNamespace, HookSetTxnID, HookOn, txHash) VALUES (?, ?, ?, ?, ?)`; + return conn.query(query, [address, HookNamespace, HookSetTxnID, HookOn, txHash]); + }); + return Promise.all(promises) + .then(() => { + resolve(); + conn.end(); + }) + .catch(err => { + conn.end(); + reject(err); + }); + }).catch(err => { + reject(err); + }); + }); + } + } -/** -* Internal DB book keeping - nothing much... -* @param {string} key - The variable's key -*/ -async function GetMiscRecord(key) { - const dbRecord = new sqlite3.Database('../db/:record:'); // Analytical records - return new Promise((resolve, reject) => { - dbRecord.get(`SELECT * FROM misc WHERE key = ?`, [key], function(err, record) { - if(err) reject(err); - dbRecord.close(); - resolve(record); - }); - }) +//#################################################################################################################### + +// Database factory +function createDatabase(type) { + switch (type) { + case 'sqlite3': + return new SQLiteDatabase(); + case 'mariadb': + return new MariaDBDatabase(); + } } -/** -* Generate a DB record for an account. -* @param {string} address -* @param {number} burnt_amount -* @param {number} minted_amount -* @param {number} burn_tx_count -* @param {number} mint_tx_count +//#################################################################################################################### + + +/**Key-in XRP Burn txs according to their accounts */ -function GenerateAccountRecord(address, burnt_amount, minted_amount, burn_tx_count, mint_tx_count) { - const dbAccount = new sqlite3.Database('../db/:account:'); // General Account Burn-To-Mint records - dbAccount.serialize(function() { - var insertAccountRecord = dbAccount.prepare("INSERT INTO account VALUES (?,?,?,?,?)"); - - insertAccountRecord.run(address, burnt_amount, minted_amount, burn_tx_count, mint_tx_count); - insertAccountRecord.finalize(); - }); - dbAccount.close(); +async function RecordBurnTx(account, amount, tx_count, date) { + const full_date = new Date((date + 946684800) * 1000).toISOString().slice(0, 10); + const display_date = new Date((date + 946684800) * 1000).toISOString().slice(0, 19); + const month_date = full_date.slice(0, 8).concat("00"); + + Log("INF", `${amount / 1000000} $XRP burnt by ${account} at ${display_date}`); + + // ACCOUNT RECORD + const record = await db.GetAccountRecord(account); + // Log("INF",`retrieving account record -> ${JSON.stringify(record)}`); + + if (record.address == account) { + db.UpdateAccountRecord(account, "burnt_amount", Number(record.burnt_amount) + amount, "burn_tx_count", record.burn_tx_count + tx_count); + } else { + db.GenerateAccountRecord(account, amount, 0, tx_count, 0, 0, 0, 0, 0, 0, 0); + } + + // HISTORY RECORD + const daily_record = await db.GetHistoryRecord("daily", full_date); + if (daily_record !== undefined) { + db.UpdateHistoryRecord("daily", full_date, "burnt_amount", Number(daily_record.burnt_amount) + amount, "burn_tx_count", daily_record.burn_tx_count + tx_count); + } else { + db.GenerateHistoryRecord("daily", full_date, amount, 0, tx_count, 0, 0, 0, 0, 0, 0, 0, 0); + } + + const monthly_record = await db.GetHistoryRecord("monthly", month_date) + if (monthly_record !== undefined) { + db.UpdateHistoryRecord("monthly", month_date, "burnt_amount", Number(monthly_record.burnt_amount) + amount, "burn_tx_count", monthly_record.burn_tx_count + tx_count); + } else { + db.GenerateHistoryRecord("monthly", month_date, amount, 0, tx_count, 0, 0, 0, 0, 0, 0, 0, 0); + } } -/** -* Generate a DB record for a specific & unique date. -* @param {string} record_name - The table's name: "daily" or "monthly" -* @param {*} date - The date: YYYY-MM-DD -* @param {number} burnt_amount -* @param {number} minted_amount -* @param {number} burn_tx_count -* @param {number} mint_tx_count -* @param {number} newly_funded_account +/**Key-in XAH Mint txs according to their accounts */ -function GenerateHistoryRecord(record_name, date, burnt_amount, minted_amount, burn_tx_count, mint_tx_count, newly_funded_account) { - const dbRecord = new sqlite3.Database('../db/:record:'); // Analytical records - dbRecord.serialize(function() { - var insertHistoryRecord = dbRecord.prepare(`INSERT INTO ${record_name} VALUES (?,?,?,?,?,?)`); - - insertHistoryRecord.run(date, burnt_amount, minted_amount, burn_tx_count, mint_tx_count, newly_funded_account) - insertHistoryRecord.finalize(); - }); - dbRecord.close(); -} +async function RecordMintTx(account, amount, tx_count, date, newly_funded_account) { + const full_date = new Date((date + 946684800) * 1000).toISOString().slice(0, 10); + const display_date = new Date((date + 946684800) * 1000).toISOString().slice(0, 19); + const month_date = full_date.slice(0, 8).concat("00"); + + Log("INF", `${amount / 1000000} $XAH minted by ${account} at ${display_date}`); + + // ACCOUNT RECORD + const record = await db.GetAccountRecord(account); + // Log("INF",`retrieving account record -> ${JSON.stringify(record)}`); + + if (record.address == account) { + db.UpdateAccountRecord(account, "minted_amount", Number(record.minted_amount) + amount, "mint_tx_count", record.mint_tx_count + tx_count); + } else { + // TODO: add this defensive functionality to mitigate any chances that we haven't indexed an account's burn txs + // await ResyncBurnTx(account); + db.GenerateAccountRecord(account, 0, amount, 0, tx_count, 0, 0, 0, 0, 0, 0); + } -function GenerateMiscRecord(key, value) { - const dbRecord = new sqlite3.Database('../db/:record:'); // Analytical records - dbRecord.serialize(function() { - var insertHistoryRecord = dbRecord.prepare(`INSERT INTO misc VALUES (?, ?)`); - - insertHistoryRecord.run(key, value); - insertHistoryRecord.finalize(); - }); - dbRecord.close(); + // HISTORY RECORD + const daily_record = await db.GetHistoryRecord("daily", full_date); + if (daily_record !== undefined) { + db.UpdateHistoryRecord("daily", full_date, "minted_amount", Number(daily_record.minted_amount) + amount, "mint_tx_count", daily_record.mint_tx_count + tx_count, "newly_funded_account", daily_record.newly_funded_account + newly_funded_account); + } else { + db.GenerateHistoryRecord("daily", full_date, 0, amount, 0, 1, 0, newly_funded_account ?? 0, 0, 0, 0, 0, 0); + } + + const monthly_record = await db.GetHistoryRecord("monthly", month_date) + if (monthly_record !== undefined) { + db.UpdateHistoryRecord("monthly", month_date, "minted_amount", Number(monthly_record.minted_amount) + amount, "mint_tx_count", monthly_record.mint_tx_count + tx_count, "newly_funded_account", monthly_record.newly_funded_account + newly_funded_account); + } else { + db.GenerateHistoryRecord("monthly", month_date, 0, amount, 0, tx_count, newly_funded_account, 0, 0, 0, 0, 0, 0); + } } -/** -* Update an account's record. -* @param {text} address - Account address -* @param {text} key1 - burn/mint_amount -* @param {number} value1 - XRP amount -* @param {text} key2 - burn/mint_count -* @param {number} value2 - tx count +/**Key-in URIToken txs according to their accounts */ -function UpdateAccountRecord(address, key1, value1, key2, value2) { - const dbAccount = new sqlite3.Database('../db/:account:'); // General Account Burn-To-Mint records - dbAccount.serialize(function() { - dbAccount.run(`UPDATE account SET ${key1} = ?, ${key2} = ? WHERE address = ?`, [value1, value2, address]) - }); - dbAccount.close(); +async function RecordURIToken(account, uritokenmint_amount, uritokenburn_amount, uritokenbuy_amount, uritokensell_amount, date) { + const full_date = new Date((date + 946684800) * 1000).toISOString().slice(0, 10); + const display_date = new Date((date + 946684800) * 1000).toISOString().slice(0, 19); + const month_date = full_date.slice(0, 8).concat("00"); + + Log("INF", `URIToken Event --> ${uritokenmint_amount}:minted ${uritokenburn_amount}:burned ${uritokenbuy_amount}:brought ${uritokensell_amount}:sold | TX by ${account} at ${display_date}`); + + // ACCOUNT RECORD + const record = await db.GetAccountRecord(account); + if (recordFlag[account] === undefined && record.address !== account) { recordFlag[account] = true; } else { recordFlag[account] = false; } // system to prevent double calling of generate, due to timing issues + + //Log("INF", ` --> account:${account} recordFlag:${recordFlag[account]} allrecordFlag:${recordFlag} RecordURIToken:${JSON.stringify(record)} <---`); + if (recordFlag[account] && record.address !== account) { + await db.GenerateAccountRecord(account, 0, 0, 0, 0, uritokenmint_amount, uritokenburn_amount, uritokenbuy_amount, uritokensell_amount, 0, 0); + delete recordFlag[account]; + } + if (record.address == account) { + await db.UpdateAccountRecord(account, "uritoken_mint_count", record.uritoken_mint_count + uritokenmint_amount, "uritoken_burn_count", record.uritoken_burn_count + uritokenburn_amount, "uritoken_buy_count", record.uritoken_buy_count + uritokenbuy_amount, "uritoken_sell_count", record.uritoken_sell_count + uritokensell_amount); + delete recordFlag[account]; + } + + // HISTORY RECORD + const daily_record = await db.GetHistoryRecord("daily", full_date); + if (drecordFlag[full_date] === undefined && daily_record == undefined) { drecordFlag[full_date] = true; } else { drecordFlag[full_date] = false; } // system to prevent double calling of generate, due to timing issues + + //Log("INF", ` --->full_date:${full_date} drecordFlag:${drecordFlag[full_date]} URIToken daily_record:${JSON.stringify(daily_record)} <---`) + if (drecordFlag[full_date] && daily_record == undefined ) { + await db.GenerateHistoryRecord("daily", full_date, 0, 0, 0, 0, 0, uritokenmint_amount, uritokenburn_amount, uritokenbuy_amount, uritokensell_amount, 0, 0); + delete drecordFlag[full_date]; + } + if (daily_record !== undefined) { + await db.UpdateHistoryRecord("daily", full_date, "uritoken_mint_count", daily_record.uritoken_mint_count + uritokenmint_amount, "uritoken_burn_count", daily_record.uritoken_burn_count + uritokenburn_amount, "uritoken_buy_count", daily_record.uritoken_buy_count + uritokenbuy_amount, "uritoken_sell_count", daily_record.uritoken_sell_count + uritokensell_amount); + delete drecordFlag[full_date]; + } + + + const monthly_record = await db.GetHistoryRecord("monthly", month_date) + if (mrecordFlag[month_date] === undefined && monthly_record == undefined) { mrecordFlag[month_date] = true; } else { mrecordFlag[month_date] = false; } // system to prevent double calling of generate, due to timing issues + + //Log("INF", ` ---->month_date:${month_date} monthly_record:${JSON.stringify(monthly_record)} <---`) + if (mrecordFlag[month_date] && monthly_record == undefined ) { + await db.GenerateHistoryRecord("monthly", month_date, 0, 0, 0, 0, 0, uritokenmint_amount, uritokenburn_amount, uritokenbuy_amount, uritokensell_amount, 0, 0); + delete mrecordFlag[month_date]; + } + if (monthly_record !== undefined) { + await db.UpdateHistoryRecord("monthly", month_date, "uritoken_mint_count", monthly_record.uritoken_mint_count + uritokenmint_amount, "uritoken_burn_count", monthly_record.uritoken_burn_count + uritokenburn_amount, "uritoken_buy_count", monthly_record.uritoken_buy_count + uritokenbuy_amount), "uritoken_sell_count", monthly_record.uritoken_sell_count + uritokensell_amount; + delete mrecordFlag[month_date]; + } } -/** -* Update date record. -* @param {text} record_name - Table name -* @param {date} date - Date (YYYY-MM-DD) -* @param {text} key1 - burn/mint_amount -* @param {number} value1 - XRP amount -* @param {text} key2 - burn/mint_count -* @param {number} value2 - tx count -* @param {text} key3 - newly_funded_account (optional) -* @param {number} value3 - Number of accounts (optional) +/**Key-in RecordHooks txs according to their accounts */ -function UpdateHistoryRecord(record_name, date, key1, value1, key2, value2, key3, value3) { - const dbRecord = new sqlite3.Database('../db/:record:'); // Analytical records - if (key3 === undefined || value3 === undefined) { - var command = `UPDATE ${record_name} SET ${key1} = ?, ${key2} = ? WHERE date = ?`; - var values = [value1, value2, date]; +async function RecordHooks(account, hookcount, hookinvokecount, date) { + const full_date = new Date((date + 946684800) * 1000).toISOString().slice(0, 10); + const display_date = new Date((date + 946684800) * 1000).toISOString().slice(0, 19); + const month_date = full_date.slice(0, 8).concat("00"); + + Log("INF", `HookEvent Event --> ${hookcount}:hookcount ${hookinvokecount}:hookinvokecount | TX by ${account} at ${display_date}`); + + // ACCOUNT RECORD + const record = await db.GetAccountRecord(account); + // Log("INF", ` ---> RecordHook:${JSON.stringify(record)} <---`) + + if (record.address == account) { + db.UpdateAccountRecord(account, "hook_count", record.hook_count + hookcount, "hookinvoke_count", record.hookinvoke_count + hookinvokecount ); } else { - var command = `UPDATE ${record_name} SET ${key1} = ?, ${key2} = ?, ${key3} = ? WHERE date = ?`; - var values = [value1, value2, value3, date]; + db.GenerateAccountRecord(account, 0, 0, 0, 0, 0, 0, 0, 0, hookcount, hookinvokecount); + } + + // HISTORY RECORD + const daily_record = await db.GetHistoryRecord("daily", full_date); + // Log("INF", ` ---> RecordHook_daily_record:${JSON.stringify(daily_record)} <---`) + if (daily_record !== undefined) { + db.UpdateHistoryRecord("daily", full_date, "hook_count", daily_record.hook_count + hookcount, "hookinvoke_count", daily_record.hookinvoke_count + hookinvokecount ); + } else { + db.GenerateHistoryRecord("daily", full_date, 0, 0, 0, 0, 0, 0, 0, 0, 0, hookcount, hookinvokecount); + } + + const monthly_record = await db.GetHistoryRecord("monthly", month_date) + if (monthly_record !== undefined) { + db.UpdateHistoryRecord("monthly", month_date, "hook_count", monthly_record.hook_count + hookcount, "hookinvoke_count", monthly_record.hookinvoke_count + hookinvokecount ); + } else { + db.GenerateHistoryRecord("monthly", month_date, 0, 0, 0, 0, 0, 0, 0, 0, 0, hookcount, hookinvokecount); } - dbRecord.serialize(function() { - dbRecord.run(command, values) - }); - dbRecord.close(); } -/** -* Update misc record (internal DB) -* @param {text} key -* @param {number} value -*/ -function UpdateMiscRecord(key, value) { - const dbRecord = new sqlite3.Database('../db/:record:'); // Analytical records - dbRecord.serialize(function() { - dbRecord.run(`UPDATE misc SET value = ? WHERE key = ?`, [value, key]) - }); - dbRecord.close(); +//#################################################################################################################### + +const sqlite3 = require('sqlite3').verbose(); + +const { connectMariaDB } = require('../db/setup.js'); +const pool = connectMariaDB(); + +const db = createDatabase(dbType); + +//#################################################################################################################### + +/* +*retry function to catch SQLITE_BUSY errors: +*use +*const GetAccountRecordWithRetry = withRetry(GetAccountRecord); +*then use GetAccountRecordWithRetry(address) to perform the same operation but with automatic retries on SQLITE_BUSY errors. + +function withRetry(fn, retries = 5, delay = 100) { + return async function(...args) { + while (true) { + try { + const result = await fn(...args); + return result; + } catch (error) { + console.log(`Database catch error, Retrying... Attempt:${currentRetry} error:${error}`); + currentRetry++; + if (error.code === 'SQLITE_BUSY' && currentRetry < retries) { + await new Promise(resolve => setTimeout(resolve, delay)); + } else { + console.log(`Database else error, Retrying... Attempt:${currentRetry}`); + throw error; + } + currentRetry++; + } + } + }; } +const GetAccountRecordWithRetry = withRetry(db.GetAccountRecord); +const GetAllAccountRecordWithRetry = withRetry(db.GetAllAccountRecord); +const GetHistoryRecordWithRetry = withRetry(db.GetHistoryRecord); +const GetAllHistoryRecordWithRetry = withRetry(db.GetAllHistoryRecord); +const GetMiscRecordWithRetry = withRetry(db.GetMiscRecord); +const GenerateAccountRecordWithRetry = withRetry(db.GenerateAccountRecord); +const GenerateHistoryRecordWithRetry = withRetry(db.GenerateHistoryRecord); +const GenerateMiscRecordWithRetry = withRetry(db.GenerateMiscRecord); +const UpdateAccountRecordWithRetry = withRetry(db.UpdateAccountRecord); +const UpdateHistoryRecordWithRetry = withRetry(db.UpdateHistoryRecord); +const UpdateMiscRecordWithRetry = withRetry(db.UpdateMiscRecord); +*/ + module.exports = { - GetAccountRecord, - GetAllAccountRecord, - GetHistoryRecord, - GetAllHistoryRecord, - GetMiscRecord, - GenerateAccountRecord, - GenerateHistoryRecord, - GenerateMiscRecord, - UpdateAccountRecord, - UpdateHistoryRecord, - UpdateMiscRecord + RecordBurnTx, + RecordMintTx, + RecordURIToken, + RecordHooks, + db, + /** + GetAccountRecordWithRetry, + GetAllAccountRecordWithRetry, + GetAllHistoryRecordWithRetry, + GetHistoryRecordWithRetry, + GetMiscRecordWithRetry, + GenerateAccountRecordWithRetry, + GenerateHistoryRecordWithRetry, + GenerateMiscRecordWithRetry, + UpdateAccountRecordWithRetry, + UpdateHistoryRecordWithRetry, + UpdateMiscRecordWithRetry + */ }; \ No newline at end of file diff --git a/db/record.js b/db/record.js deleted file mode 100755 index 346cbf0..0000000 --- a/db/record.js +++ /dev/null @@ -1,201 +0,0 @@ -const { Log } = require("../log/logger.js"); -const sqlite3 = require('sqlite3').verbose(); - -// We don't use db/manager.js/ here. - -var dbAccount = null; -var dbRecord = null; - -/** Open Record DB */ -function OpenAccountDB() { - dbAccount = new sqlite3.Database('../db/:account:'); // General Account Burn-To-Mint records -} - -/** Open Record DB */ -function OpenRecordDB() { - dbRecord = new sqlite3.Database('../db/:record:'); // Analytical records -} - -/** -* Get an account's B2M record. -* @param {string} address - Account address -*/ -async function GetAccountRecord(address) { - return new Promise((resolve, reject) => { - dbAccount.get("SELECT * FROM account WHERE address = ?", [address], function(err, record) { - if(err) reject(err); - resolve(record); - }); - }) -} - -/** -* Get a specific date's B2M performance. -* @param {string} record_name - The table's name: "daily" or "monthly" -* @param {string} date - The date - YYYY-MM-DD -* @returns -*/ -async function GetHistoryRecord(record_name, date) { - return new Promise((resolve, reject) => { - dbRecord.get(`SELECT * FROM ${record_name} WHERE date = ?`, [date], function(err, record) { - if(err) reject(err); - resolve(record); - }); - }) -} - -/** -* Generate a DB record for an account. -* @param {string} address -* @param {number} burnt_amount -* @param {number} minted_amount -* @param {number} burn_tx_count -* @param {number} mint_tx_count -*/ -function GenerateAccountRecord(address, burnt_amount, minted_amount, burn_tx_count, mint_tx_count) { - dbAccount.serialize(function() { - var insertAccountRecord = dbAccount.prepare("INSERT INTO account VALUES (?,?,?,?,?)"); - - insertAccountRecord.run(address, burnt_amount, minted_amount, burn_tx_count, mint_tx_count); - insertAccountRecord.finalize(); - }); -} - -/** -* Generate a DB record for a specific & unique date. -* @param {string} record_name - The table's name: "daily" or "monthly" -* @param {*} date - The date: YYYY-MM-DD -* @param {number} burnt_amount -* @param {number} minted_amount -* @param {number} burn_tx_count -* @param {number} mint_tx_count -* @param {number} newly_funded_account -*/ -function GenerateHistoryRecord(record_name, date, burnt_amount, minted_amount, burn_tx_count, mint_tx_count, newly_funded_account) { - dbRecord.serialize(function() { - var insertHistoryRecord = dbRecord.prepare(`INSERT INTO ${record_name} VALUES (?,?,?,?,?,?)`); - - insertHistoryRecord.run(date, burnt_amount, minted_amount, burn_tx_count, mint_tx_count, newly_funded_account) - insertHistoryRecord.finalize(); - }); -} - -/** -* Update an account's record. -* @param {text} address - Account address -* @param {text} key1 - burn/mint_amount -* @param {number} value1 - XRP amount -* @param {text} key2 - burn/mint_count -* @param {number} value2 - tx count -*/ -function UpdateAccountRecord(address, key1, value1, key2, value2) { - dbAccount.serialize(function() { - dbAccount.run(`UPDATE account SET ${key1} = ?, ${key2} = ? WHERE address = ?`, [value1, value2, address]) - }); -} - -/** -* Update date record. -* @param {text} record_name - Table name -* @param {date} date - Date (YYYY-MM-DD) -* @param {text} key1 - burn/mint_amount -* @param {number} value1 - XRP amount -* @param {text} key2 - burn/mint_count -* @param {number} value2 - tx count -* @param {text} key3 - newly_funded_account (optional) -* @param {number} value3 - Number of accounts (optional) -*/ -function UpdateHistoryRecord(record_name, date, key1, value1, key2, value2, key3, value3) { - if (key3 === undefined || value3 === undefined) { - var command = `UPDATE ${record_name} SET ${key1} = ?, ${key2} = ? WHERE date = ?`; - var values = [value1, value2, date]; - } else { - var command = `UPDATE ${record_name} SET ${key1} = ?, ${key2} = ?, ${key3} = ? WHERE date = ?`; - var values = [value1, value2, value3, date]; - } - dbRecord.serialize(function() { - dbRecord.run(command, values) - }); -} - -/** -* Key-in Burn txs according to their accounts -*/ -async function RecordBurnTx(account, amount, tx_count, date) { - const full_date = new Date((date + 946684800) * 1000).toISOString().slice(0, 10); - const display_date = new Date((date + 946684800) * 1000).toISOString().slice(0, 19); - const month_date = full_date.slice(0, 8).concat("00"); - - Log("INF", `${amount / 1000000} $XRP burnt by ${account} at ${display_date}`); - - OpenAccountDB(); - // ACCOUNT RECORD - const record = await GetAccountRecord(account); - - if (record !== undefined) { - UpdateAccountRecord(account, "burnt_amount", record.burnt_amount + amount, "burn_tx_count", record.burn_tx_count + tx_count); - } else { - GenerateAccountRecord(account, amount, 0, tx_count, 0); - } - dbAccount.close(); - - OpenRecordDB(); - // HISTORY RECORD - const daily_record = await GetHistoryRecord("daily", full_date); - const monthly_record = await GetHistoryRecord("monthly", month_date) - - if (daily_record !== undefined) { - UpdateHistoryRecord("daily", full_date, "burnt_amount", daily_record.burnt_amount + amount, "burn_tx_count", daily_record.burn_tx_count + tx_count); - } else { - GenerateHistoryRecord("daily", full_date, amount, 0, tx_count, 0, 0); - } - if (monthly_record !== undefined) { - UpdateHistoryRecord("monthly", month_date, "burnt_amount", monthly_record.burnt_amount + amount, "burn_tx_count", monthly_record.burn_tx_count + tx_count); - } else { - GenerateHistoryRecord("monthly", month_date, amount, 0, tx_count, 0, 0); - } - dbRecord.close(); -} - -/** -* Key-in Mint txs according to their accounts -*/ -async function RecordMintTx(account, amount, tx_count, date, newly_funded_account) { - const full_date = new Date((date + 946684800) * 1000).toISOString().slice(0, 10); - const display_date = new Date((date + 946684800) * 1000).toISOString().slice(0, 19); - const month_date = full_date.slice(0, 8).concat("00"); - - Log("INF", `${amount / 1000000} $XAH minted by ${account} at ${display_date}`); - - OpenAccountDB(); - // ACCOUNT RECORD - const record = await GetAccountRecord(account); - - if (record !== undefined) { - UpdateAccountRecord(account, "minted_amount", record.minted_amount + amount, "mint_tx_count", record.mint_tx_count + tx_count); - } else { - // TODO: add this defensive functionality to mitigate any chances that we haven't indexed an account's burn txs - // await ResyncBurnTx(account); - GenerateAccountRecord(account, 0, amount, 0, tx_count); - } - dbAccount.close(); - - OpenRecordDB(); - // HISTORY RECORD - const daily_record = await GetHistoryRecord("daily", full_date); - const monthly_record = await GetHistoryRecord("monthly", month_date) - - if (daily_record !== undefined) { - UpdateHistoryRecord("daily", full_date, "minted_amount", daily_record.minted_amount + amount, "mint_tx_count", daily_record.mint_tx_count + tx_count, "newly_funded_account", daily_record.newly_funded_account + newly_funded_account); - } else { - GenerateHistoryRecord("daily", full_date, 0, amount, 0, 1, 0, newly_funded_account ?? 0); - } - if (monthly_record !== undefined) { - UpdateHistoryRecord("monthly", month_date, "minted_amount", monthly_record.minted_amount + amount, "mint_tx_count", monthly_record.mint_tx_count + tx_count, "newly_funded_account", monthly_record.newly_funded_account + newly_funded_account); - } else { - GenerateHistoryRecord("monthly", month_date, 0, amount, 0, tx_count, newly_funded_account); - } - dbRecord.close(); -} - -module.exports = { RecordBurnTx, RecordMintTx }; \ No newline at end of file diff --git a/db/setup.js b/db/setup.js index 3ac5641..83a5fc1 100755 --- a/db/setup.js +++ b/db/setup.js @@ -1,17 +1,75 @@ -const sqlite3 = require('sqlite3').verbose(); - -// Record: B2M Analytics (XRPL & Xahau) -const dbRecord = new sqlite3.Database('../db/:record:'); // Analytical records -dbRecord.serialize(function() { - dbRecord.run("CREATE TABLE IF NOT EXISTS misc (key VARCHAR(255) PRIMARY KEY, value VARCHAR(255) NOT NULL)") - dbRecord.run("CREATE TABLE IF NOT EXISTS daily (date DATE PRIMARY KEY, burnt_amount INT NOT NULL, minted_amount INT NOT NULL, burn_tx_count INT NOT NULL, mint_tx_count INT NOT NULL, newly_funded_account INT NOT NULL)"); - dbRecord.run("CREATE TABLE IF NOT EXISTS monthly (date DATE PRIMARY KEY, burnt_amount INT NOT NULL, minted_amount INT NOT NULL, burn_tx_count INT NOT NULL, mint_tx_count INT NOT NULL, newly_funded_account INT NOT NULL)"); -}); -dbRecord.close(); - -// Record: XRPLCP Accounts (XRPL & Xahau) -const dbAccount = new sqlite3.Database('../db/:account:'); // General Account Burn-To-Mint records -dbAccount.serialize(function() { - dbAccount.run("CREATE TABLE IF NOT EXISTS account (address VARCHAR(35) PRIMARY KEY, burnt_amount INT NOT NULL, minted_amount INT NOT NULL, burn_tx_count INT NOT NULL, mint_tx_count INT NOT NULL)"); -}); -dbAccount.close(); \ No newline at end of file +const dotenv = require("dotenv").config({path:"./.env"}); +const dbType = process.env.DB_TYPE; + +if (dbType === 'sqlite3') { + setupSQLite(); +} else if (dbType === 'mariadb') { + setupMariaDB(); +} else { + console.error(`Unsupported database type, needs to be either sqlite3, or mariadb and not: ${dbType}`); + process.exit(1); +} + +/// #### Setup sqlite3 +function setupSQLite() { + const sqlite3 = require('sqlite3').verbose(); + + // Record: B2M Analytics (XRPL & Xahau) + const dbRecord = new sqlite3.Database('./db/_record.sqlite3'); // Analytical records + dbRecord.serialize(function() { + dbRecord.run("CREATE TABLE IF NOT EXISTS misc (key VARCHAR(255) PRIMARY KEY, value VARCHAR(255) NOT NULL)"); + dbRecord.run("CREATE TABLE IF NOT EXISTS daily (date DATE PRIMARY KEY, burnt_amount BIGINT NOT NULL, minted_amount BIGINT NOT NULL, burn_tx_count BIGINT NOT NULL, mint_tx_count BIGINT NOT NULL, newly_funded_account BIGINT NOT NULL, uritoken_mint_count INT NOT NULL, uritoken_burn_count INT NOT NULL, uritoken_buy_count INT NOT NULL, uritoken_sell_count INT NOT NULL, hook_count INT NOT NULL, hookinvoke_count INT NOT NULL)"); + dbRecord.run("CREATE TABLE IF NOT EXISTS monthly (date DATE PRIMARY KEY, burnt_amount BIGINT NOT NULL, minted_amount BIGINT NOT NULL, burn_tx_count BIGINT NOT NULL, mint_tx_count BIGINT NOT NULL, newly_funded_account BIGINT NOT NULL, uritoken_mint_count INT NOT NULL, uritoken_burn_count INT NOT NULL, uritoken_buy_count INT NOT NULL, uritoken_sell_count INT NOT NULL, hook_count INT NOT NULL, hookinvoke_count INT NOT NULL)"); + }); + dbRecord.close(); + + // Record: XRPLCP Accounts (XRPL & Xahau) + const dbAccount = new sqlite3.Database('./db/_account.sqlite3'); // General Account Burn-To-Mint records + dbAccount.serialize(function() { + dbAccount.run("CREATE TABLE IF NOT EXISTS account (address VARCHAR(35) PRIMARY KEY, burnt_amount BIGINT NOT NULL, minted_amount BIGINT NOT NULL, burn_tx_count BIGINT NOT NULL, mint_tx_count BIGINT NOT NULL, uritoken_mint_count INT NOT NULL, uritoken_burn_count INT NOT NULL, uritoken_buy_count INT NOT NULL, uritoken_sell_count INT NOT NULL, hook_count INT NOT NULL, hookinvoke_count INT NOT NULL)"); + dbAccount.run("CREATE TABLE IF NOT EXISTS uritokens (id INT AUTO_INCREMENT PRIMARY KEY, address VARCHAR(35), uri VARCHAR(512) NOT NULL, URITokenID VARCHAR(512) NOT NULL, txHash VARCHAR(256) NOT NULL, hook_count INT NOT NULL, hookinvoke_count INT NOT NULL, FOREIGN KEY (address) REFERENCES account(address))"); + dbAccount.run("CREATE TABLE IF NOT EXISTS hooks (id INT AUTO_INCREMENT PRIMARY KEY, address VARCHAR(35), HookNamespace VARCHAR(512) NOT NULL, HookSetTxnID VARCHAR(256) NOT NULL, txHash VARCHAR(256) NOT NULL, hook_count INT NOT NULL, hookinvoke_count INT NOT NULL, FOREIGN KEY (address) REFERENCES account(address))"); + + }); + dbAccount.close(); +} + +/// ### connection pool for MariaDB +function connectMariaDB() { + const mariadb = require('mariadb'); + const pool = mariadb.createPool({ + host: process.env.DB_HOST, + port: process.env.DB_PORT, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + connectionLimit: 5 + }); + return pool; +} + +/// ### Setup MariaDB +async function setupMariaDB() { + const pool = connectMariaDB(); + let conn; + try { + conn = await pool.getConnection(); + await conn.query("CREATE TABLE IF NOT EXISTS misc (`key` VARCHAR(255) PRIMARY KEY, value VARCHAR(255) NOT NULL)"); + await conn.query("CREATE TABLE IF NOT EXISTS account (address VARCHAR(35) PRIMARY KEY, burnt_amount BIGINT NOT NULL, minted_amount BIGINT NOT NULL, burn_tx_count INT NOT NULL, mint_tx_count INT NOT NULL, uritoken_mint_count INT NOT NULL, uritoken_burn_count INT NOT NULL, uritoken_buy_count INT NOT NULL, uritoken_sell_count INT NOT NULL, hook_count INT NOT NULL, hookinvoke_count INT NOT NULL)"); + await conn.query("CREATE TABLE IF NOT EXISTS daily (date DATE PRIMARY KEY, burnt_amount BIGINT NOT NULL, minted_amount BIGINT NOT NULL, burn_tx_count INT NOT NULL, mint_tx_count INT NOT NULL, newly_funded_account INT NOT NULL, uritoken_mint_count INT NOT NULL, uritoken_burn_count INT NOT NULL, uritoken_buy_count INT NOT NULL, uritoken_sell_count INT NOT NULL, hook_count INT NOT NULL, hookinvoke_count INT NOT NULL)"); + await conn.query("CREATE TABLE IF NOT EXISTS monthly (date DATE PRIMARY KEY, burnt_amount BIGINT NOT NULL, minted_amount BIGINT NOT NULL, burn_tx_count INT NOT NULL, mint_tx_count INT NOT NULL, newly_funded_account INT NOT NULL, uritoken_mint_count INT NOT NULL, uritoken_burn_count INT NOT NULL, uritoken_buy_count INT NOT NULL, uritoken_sell_count INT NOT NULL, hook_count INT NOT NULL, hookinvoke_count INT NOT NULL)"); + await conn.query("CREATE TABLE IF NOT EXISTS uritokens (id INT AUTO_INCREMENT PRIMARY KEY, address VARCHAR(35), URITokenID VARCHAR(512) NOT NULL, uri VARCHAR(512) NOT NULL, txHash VARCHAR(256) NOT NULL, FOREIGN KEY (address) REFERENCES account(address))"); + await conn.query("CREATE TABLE IF NOT EXISTS hooks (id INT AUTO_INCREMENT PRIMARY KEY, address VARCHAR(35), HookNamespace VARCHAR(64) NOT NULL, HookSetTxnID VARCHAR(256) NOT NULL, HookOn VARCHAR(68) NOT NULL, txHash VARCHAR(256) NOT NULL, FOREIGN KEY (address) REFERENCES account(address))"); + + } catch (err) { + throw err; + } finally { + if (conn) return conn.end(); + } +} + +console.log(`Database type:${dbType} has been checked and is ready..`); + +module.exports = { + connectMariaDB +} \ No newline at end of file diff --git a/log/logger.js b/log/logger.js index f6c2691..28f7ff5 100644 --- a/log/logger.js +++ b/log/logger.js @@ -12,13 +12,13 @@ function Log(type, message) { switch (type) { case "ERR": - fs.appendFileSync(`../log/${type}.txt`, message); + fs.appendFileSync(`./log/${type}.txt`, message); break; case "WRN": - fs.appendFileSync(`../log/${type}.txt`, message); + fs.appendFileSync(`./log/${type}.txt`, message); break; case "INF": - fs.appendFileSync(`../log/${type}.txt`, message); + fs.appendFileSync(`./log/${type}.txt`, message); break; } }; diff --git a/package-lock.json b/package-lock.json index 1e91d09..ea652ba 100755 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,8 @@ "license": "ISC", "dependencies": { "dotenv": "^16.3.1", + "express": "^4.18.2", + "mariadb": "^3.2.2", "prompt-sync": "^4.2.0", "sqlite3": "^5.1.6", "xrpl-client": "^2.2.0" @@ -73,11 +75,33 @@ "node": ">= 6" } }, + "node_modules/@types/geojson": { + "version": "7946.0.13", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", + "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==" + }, + "node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -139,11 +163,63 @@ "node": ">=10" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -165,6 +241,14 @@ "node": ">=6.14.2" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cacache": { "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", @@ -194,6 +278,19 @@ "node": ">= 10" } }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -229,6 +326,38 @@ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, "node_modules/d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -254,11 +383,49 @@ } } }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/detect-libc": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", @@ -278,11 +445,24 @@ "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/encoding": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", @@ -340,6 +520,73 @@ "ext": "^1.1.2" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -353,6 +600,52 @@ "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -369,6 +662,14 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gauge": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", @@ -407,6 +708,20 @@ "node": ">=8" } }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -426,23 +741,93 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "optional": true }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "optional": true }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/http-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", @@ -482,7 +867,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -534,6 +918,14 @@ "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "optional": true }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -619,6 +1011,80 @@ "node": ">= 10" } }, + "node_modules/mariadb": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.2.2.tgz", + "integrity": "sha512-9ClJCFsLcK7WnPXVxuKGd7p0CBvNch3i5nwAf1HEqERj7RV60DG/0dJu4DfO33gpYQd9Cr4jq17O76/2VjSbkg==", + "dependencies": { + "@types/geojson": "^7946.0.10", + "@types/node": "^17.0.45", + "denque": "^2.1.0", + "iconv-lite": "^0.6.3", + "lru-cache": "^10.0.1" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mariadb/node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -738,7 +1204,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "optional": true, "engines": { "node": ">= 0.6" } @@ -907,6 +1372,25 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -930,6 +1414,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -938,6 +1430,11 @@ "node": ">=0.10.0" } }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -965,6 +1462,65 @@ "strip-ansi": "^5.0.0" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -1023,8 +1579,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "optional": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { "version": "7.5.4", @@ -1040,11 +1595,98 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -1122,6 +1764,14 @@ "node": ">= 8" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -1197,6 +1847,14 @@ "node": ">=8" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -1207,6 +1865,18 @@ "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -1233,6 +1903,14 @@ "imurmurhash": "^0.1.4" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/utf-8-validate": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", @@ -1250,6 +1928,22 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index e7d3e9f..2e70f2c 100755 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "XRPL", "Xahau" ], - "author": "Wo Jake", + "authors": "Wo Jake, gadget78", "license": "ISC", "bugs": { "url": "https://github.com/Xahau/B2M-indexer/issues" @@ -24,8 +24,10 @@ "homepage": "https://github.com/Xahau/B2M-indexer#readme", "dependencies": { "dotenv": "^16.3.1", - "prompt-sync": "^4.2.0", + "express": "^4.18.2", "sqlite3": "^5.1.6", + "mariadb": "^3.2.2", + "prompt-sync": "^4.2.0", "xrpl-client": "^2.2.0" } } diff --git a/src/api.js b/src/api.js new file mode 100644 index 0000000..cba2a07 --- /dev/null +++ b/src/api.js @@ -0,0 +1,104 @@ +console.log('API server starting .....'); +const express = require('express'); +const app = express(); +const dbManager = require("../db/manager"); +const dotenv = require("dotenv").config({path:"./.env"}); + +// GET route for stats +app.get('/', async (req, res) => { + try { + // get the last indexed ledger, and monthly history record + var syncedXrplLedgerIndex = await dbManager.db.GetMiscRecord("lastSyncedXrplLedgerIndex"); + var syncedXahauLedgerIndex = await dbManager.db.GetMiscRecord("lastSyncedXahauLedgerIndex"); + var data_MonthlyHistoryRecord = await dbManager.db.GetAllHistoryRecord("monthly"); + // setup vars for loop + var burnt_amount = 0; + var minted_amount = 0; + var total_funded_accounts = 0; + var uritoken_mint_count = 0; + var uritoken_burn_count = 0; + var uritoken_buy_count = 0; + var uritoken_sell_count = 0; + var hook_count = 0; + var hookinvoke_count = 0; + const endpoints = ["/all","/account/","/history/daily","/history/monthy","/uri/"]; + + //console.log(`setting up -> ${uritoken_mint_count} before. raw data -> ${JSON.stringify(data_MonthlyHistoryRecord)} `); + data_MonthlyHistoryRecord.forEach(record => { + burnt_amount += Number(record.burnt_amount); + minted_amount += Number(record.minted_amount); + total_funded_accounts += record.newly_funded_account; + uritoken_mint_count += record.uritoken_mint_count; + uritoken_burn_count += record.uritoken_burn_count; + uritoken_buy_count += record.uritoken_buy_count; + uritoken_sell_count += record.uritoken_sell_count; + hook_count += record.hook_count; + hookinvoke_count += record.hookinvoke_count; + }); + + // create response json + const response = { + endpoints_include: endpoints, + lastSyncedXrplLedgerIndex: syncedXrplLedgerIndex.value, + lastSyncedXahauLedgerIndex: syncedXahauLedgerIndex.value, + fundedAccounts: total_funded_accounts, + burntXRP: burnt_amount / 1000000, // Converted from drops + mintedXAH: minted_amount / 1000000, // Converted from drops + unmintedXAH: (burnt_amount - minted_amount) / 1000000, // Converted from drops + uritoken_mint_count: uritoken_mint_count, + uritoken_burn_count: uritoken_burn_count, + uritoken_buy_count: uritoken_buy_count, + uritoken_sell_count: uritoken_sell_count, + hook_count: hook_count, + hookinvoke_count: hookinvoke_count + }; + console.log(`sent API -> /`); + // send + res.json(response); + } catch (error) { + console.error(error); + res.status(500).json({ error: 'An error occurred while fetching B2M statistics.' }); + } +}); + +// GET route for retrieving all account records +app.get('/all', async (req, res) => { + // Code to handle GET request for all account records + const data_AllAccountRecord = await dbManager.db.GetAllAccountRecord(); + res.json(data_AllAccountRecord); +}); + +// GET route for retrieving a specific account record by address +app.get('/account/:address', async (req, res) => { + const accountAddress = req.params.address; + // Code to handle GET request for a specific account record + if (accountAddress == "all") { data_AccountRecord = await dbManager.db.GetAllAccountRecord();} + else { data_AccountRecord = await dbManager.db.GetAccountRecord(accountAddress); } + res.json(data_AccountRecord); +}); + +// GET route for retrieving all history records (daily or monthly) +app.get('/history/:tableName', async (req, res) => { + const tableName = req.params.tableName; + // Code to handle GET request for all history records (daily or monthly) + if (tableName !== "daily" && tableName !== "monthly") { dataAllHistoryRecord = "{error: please state daily, or monthly}"; }; + if (tableName == "daily" || tableName == "monthly") { dataAllHistoryRecord = await dbManager.db.GetAllHistoryRecord(tableName); }; + console.log(`sent API -> /history/ res:{JSON.stringify(res)} req:${JSON.stringify(req.params.tableName)} tableName:${tableName} data:${dataAllHistoryRecord}`); + res.json(dataAllHistoryRecord); +}); + +// GET route for retrieving all history records (daily or monthly) +app.get('/uri/:uri', async (req, res) => { + const uri = req.params.uri; + // Code to handle GET request for all history records (daily or monthly) + const data_GetURIRecord = await dbManager.db.GetURIRecord(uri); + res.json(data_GetURIRecord); +}); + +// Set the desired port +const port = process.env.API_PORT || 3000; + +// Start the Express server +app.listen(port, () => { + console.log(`API server is now running on port ${port}`); +}); diff --git a/src/main.js b/src/main.js index 5dd9fd5..36e277d 100755 --- a/src/main.js +++ b/src/main.js @@ -1,50 +1,336 @@ -const dbSetup = require("../db/setup"); - -const { XrplClient } = require("xrpl-client"); - -const dbManager = require("../db/manager.js"); -const record = require("../db/record.js"); -const { Log } = require("../log/logger.js"); - -const dotenv = require("dotenv").config({path:"../.env"}); - // B2M-indexer - Listen & index Burn2Mint transactions on the XRPL & Xahau. // Please provide a `.env` file. Original copy comes with `.env.sample` for testnet use (for now, until xahau launches). // XRPL Network ID; 0 // Xahau Network ID; TBD +// --- For testing purposes --- +// Testnet: You can just put in a random, but close enough ledger index to not spend too much time indexing through the ledger. +// NOTE: In production, if you don't start from the first ledger(s) that contain the first Burn & Mint tx, the account records will be wrong. + + +const { Log } = require("../log/logger"); +const dotenv = require("dotenv").config({path:"./.env"}); + +const dbSetup = require("../db/setup"); +const dbManager = require("../db/manager"); +const { XrplClient } = require("xrpl-client"); + +// ### Spawn a child process to run the API code in a separate thread (if enabled in .env file) +const API_ENABLED = [ process.env.API_ENABLED ] +if (API_ENABLED == "true") { + const { spawn } = require('child_process'); + const { stringify } = require("querystring"); + const apiProcessa = spawn('node', ['src/api.js'], { + stdio: ['inherit', 'inherit', 'inherit'], + }); +} else {Log("WRN",` ** API server disabled (enable via .env)`)} + + // Nodes to connect to (Xahau & XRPL) const xrplClients = [process.env.XRPL_CLIENT]; const xahauClients = [process.env.XAHAU_CLIENT]; - const clientConfig = { assumeOfflineAfterSeconds: 30, maxConnectionAttempts: 10, - connectAttemptTimeoutSeconds: 3, + connectAttemptTimeoutSeconds: 5, }; - const xrplClient = new XrplClient(xrplClients, clientConfig); const xahauClient = new XrplClient(xahauClients, clientConfig); -// --- For testing purposes --- -// Testnet: You can just put in a random, but close enough ledger index to not spend too much time indexing through the ledger. -// NOTE: In production, if you don't start from the first ledger(s) that contain the first Burn & Mint tx, the account records will be wrong. /** The ledger containing the first **Burn** tx */ var ledgerBurn = process.env.XRPL_LEDGER_INDEX; /** The ledger where XahauGenesis went live */ var ledgerMint = process.env.XAHAU_LEDGER_INDEX; -// Tx stream state +// other var setups for loops etc var xrplListener = false; var xahauListener = false; var temporaryBurnAccountRecord = {}; var temporaryLastSyncedXrplLedger = 0; var temporaryMintAccountRecord = {}; +var temporaryURITokenRecord = {}; +var temporaryHooksRecord = {}; var temporaryLastSyncedXahauLedger = 0; +// MAIN proccesing function for Xahau +async function processXahauTransactions(incomingTX, ledger, ledgerID) { + + //prepare the incomingTX (so this processXahauTransactions() function can process "ledgers" and "transaction" types) + var xahauLedger = {}; + if (incomingTX.transaction) { + xahauLedger.ledger = incomingTX; + if (xahauLedger.ledger.meta) {xahauLedger.ledger.transaction.meta = xahauLedger.ledger.meta}; + if (xahauLedger.ledger.metaData) {xahauLedger.ledger.transaction.meta = xahauLedger.ledger.metaData}; + xahauLedger.ledger.transactions = [ incomingTX.transaction ]; + xahauLedger.ledger.close_time = incomingTX.transaction.date; + delete xahauLedger.ledger.transaction; // not sure i need to do this ? + } else xahauLedger = incomingTX; + + try { + for (const tx of xahauLedger.ledger.transactions) { + + // check and establish a common tx.meta location + if(tx.metaData) { + tx.meta = tx.metaData; + delete tx.metaData; + }; + + // Import detection + if (tx.TransactionType === "Import" && (tx.meta.TransactionResult === "tesSUCCESS" || tx.engine_result === "tesSUCCESS")) { + var newlyFundedAccount = 0; + var import_amount = null; + + tx.meta.AffectedNodes.forEach(meta => { + if (meta.hasOwnProperty("CreatedNode") && meta.CreatedNode.LedgerEntryType === "AccountRoot") { + newlyFundedAccount = 1; + import_amount = parseInt(meta.CreatedNode.NewFields.Balance); + } + if (meta.hasOwnProperty("ModifiedNode") && meta.ModifiedNode.LedgerEntryType === "AccountRoot") { + import_amount = (parseInt(meta.ModifiedNode.FinalFields.Balance) + parseInt(tx.Fee)) - parseInt(meta.ModifiedNode.PreviousFields.Balance ?? meta.ModifiedNode.FinalFields.Balance); + } + }) + + var accountMintRecord = temporaryMintAccountRecord[tx.Account]; + if (accountMintRecord === undefined) { + temporaryMintAccountRecord[tx.Account] = { + amount: import_amount, + tx_count: 1, + date: xahauLedger.ledger.close_time, + newly_funded_account: newlyFundedAccount + }; + } else { + temporaryMintAccountRecord[tx.Account].amount = accountMintRecord.amount + import_amount; + temporaryMintAccountRecord[tx.Account].tx_count = accountMintRecord.tx_count + 1; + temporaryMintAccountRecord[tx.transaction.Account].newly_funded_account = newlyFundedAccount; + } + } + + // URITokenMint detection + if (tx.TransactionType === "URITokenMint" && (tx.meta.TransactionResult === "tesSUCCESS" || tx.engine_result === "tesSUCCESS")) { + //cyle through tx to get the URITokenID, so we can capture the new owner of int (just in case of mint on behalf of another account?) + if (tx.meta && tx.meta.AffectedNodes) { + for (let node of tx.meta.AffectedNodes) { + if (node.CreatedNode && node.CreatedNode.LedgerEntryType === 'URIToken') { + var mintAccount = node.CreatedNode.NewFields.Owner; + var URITokenID = node.CreatedNode.LedgerIndex; + } + } + } + //Log("INF",`txURIToken setup: ${txURIToken}`); + var uriTokenRecord = temporaryURITokenRecord[mintAccount]; + if (uriTokenRecord === undefined) { + temporaryURITokenRecord[mintAccount] = { + uritokenmint: [ { count: 1, details: { URITokenID: [ URITokenID ], URI: [ tx.URI ] } }, ], totalmintcount: 1, + uritokenburn: { count: 0, URITokenID: [] }, + uritokenbuy: [ { count: 0, details: { URITokenID: [], URI: [], sellAccount: [] } }, ], totalbuycount: 0, + uritokensell: [ { count: 0, details: { URITokenID: [], URI: [], buyAccount: [] } }, ], totalsellcount: 0, + hash: tx.hash, + date: xahauLedger.ledger.close_time + }; + } else { + temporaryURITokenRecord[tx.Account].totalmintcount = uriTokenRecord.totalmintcount + 1; + temporaryURITokenRecord[tx.Account].uritokenmint.push({ count: temporaryURITokenRecord[tx.Account].totalmintcount, details: { URITokenID: [ URITokenID ], URI: [ tx.URI ] } }); + } + } + + // URITokenBurn detection + if (tx.TransactionType === "URITokenBurn" && (tx.meta.TransactionResult === "tesSUCCESS" || tx.engine_result === "tesSUCCESS")) { + var uriTokenRecord = temporaryURITokenRecord[tx.Account]; + if (uriTokenRecord === undefined) { + temporaryURITokenRecord[tx.Account] = { + uritokenmint: [ { count: 0, details: { URITokenID: [], URI: [] } }, ], totalmintcount: 0, + uritokenburn: { count: 1, URITokenID: [ tx.URITokenID ] }, + uritokenbuy: [ { count: 0, details: { URITokenID: [], URI: [], sellAccount: [] } }, ], totalbuycount: 0, + uritokensell: [ { count: 0, details: { URITokenID: [], URI: [], buyAccount: [] } }, ], totalsellcount: 0, + hash: tx.hash, + date: xahauLedger.ledger.close_time + }; + } else { + temporaryURITokenRecord[tx.Account].uritokenburn.count = uriTokenRecord.uritokenburn.count + 1; + temporaryURITokenRecord[tx.Account].uritokenburn.URITokenID.push(tx.URITokenID); + } + } + + // URITokenBuy detection + if (tx.TransactionType === "URITokenBuy" && (tx.meta.TransactionResult === "tesSUCCESS" || tx.engine_result === "tesSUCCESS")) { + //get the bbuyers AND the sellers account + //tx.meta.AffectedNodes.ModifiedNode.FinalFields.Owner = buyer + //tx.meta.AffectedNodes.ModifiedNode.PreviousFields.Owner = seller + if (tx.meta && tx.meta.AffectedNodes) { + for (let node of tx.meta.AffectedNodes) { + if (node.ModifiedNode && node.ModifiedNode.LedgerEntryType === 'URIToken') { + var buyAccount = node.ModifiedNode.FinalFields.Owner; + var sellAccount = node.ModifiedNode.PreviousFields.Owner; + var URI = node.ModifiedNode.FinalFields.URI; + } + } + } + //Log("INF",`URITokenBuy -> URITokenID:${tx.URITokenID} URI:${URI} Buyer:${buyAccount} Seller:${sellAccount}`); + + // buy section + var uriTokenRecord = temporaryURITokenRecord[buyAccount]; + if (uriTokenRecord === undefined) { + temporaryURITokenRecord[buyAccount] = { + uritokenmint: [ { count: 0, details: { URITokenID: [], URI: [] } }, ], totalmintcount: 0, + uritokenburn: { count: 0, URITokenID: [] }, + uritokenbuy: [ { count: 1, details: { URITokenID: [ tx.URITokenID ], URI: [ URI ], sellAccount: sellAccount } }, ], totalbuycount: 1, + uritokensell: [ { count: 0, details: { URITokenID: [], URI: [], buyAccount: [] } }, ], totalsellcount: 0, + hash: tx.hash, + date: xahauLedger.ledger.close_time + }; + } else { + temporaryURITokenRecord[buyAccount].totalbuycount = uriTokenRecord.totalbuycount + 1; + temporaryURITokenRecord[buyAccount].uritokenbuy.push({ count: temporaryURITokenRecord[buyAccount].totalbuycount, details: { URITokenID: [ tx.URITokenID ], URI: [ URI ], sellAccount: sellAccount } }); + } + + //sell section + var uriTokenRecord = temporaryURITokenRecord[sellAccount]; + if (uriTokenRecord === undefined) { + temporaryURITokenRecord[sellAccount] = { + uritokenmint: [ { count: 0, details: { URITokenID: [], URI: [] } }, ], totalmintcount: 0, + uritokenburn: { count: 0, URITokenID: [] }, + uritokenbuy: [ { count: 0, details: { URITokenID: [], URI: [], sellAccount: [] } }, ], totalbuycount: 0, + uritokensell: [ { count: 1, details: { URITokenID: [ tx.URITokenID ] , URI: [ URI ], buyAccount: buyAccount } }, ], totalsellcount: 1, + hash: tx.hash, + date: xahauLedger.ledger.close_time + }; + } else { + temporaryURITokenRecord[sellAccount].totalsellcount = uriTokenRecord.totalsellcount + 1; + temporaryURITokenRecord[sellAccount].uritokensell.push({ count: temporaryURITokenRecord[sellAccount].totalsellcount, details: { URITokenID: [ tx.URITokenID ] , URI: [ URI ], buyAccount: buyAccount } }); + } + } + + // SetHook detection + if (tx.TransactionType === "SetHook" && (tx.meta.TransactionResult === "tesSUCCESS" || tx.engine_result === "tesSUCCESS")) { + // cyle through tx to get the HookDefinition, so we can capture all the main hook info + // this needs refining to pick when they are "minted", when they are updated, when deleted etc and then updated accordingingly using the hookNameSpace as the identifier, + // also include other data to be parsed/updated, including adding many hooks in one TX, + // icluding parsing of tx.Hooks.Hook (and cycle through) fully too + if (tx.meta && tx.meta.AffectedNodes) { + for (let node of tx.meta.AffectedNodes) { + if (node.CreatedNode && node.CreatedNode.LedgerEntryType === 'HookDefinition') { + var HookNamespace = node.CreatedNode.NewFields.HookNamespace; + var HookHash = node.CreatedNode.NewFields.HookHash; + var HookSetTxnID = node.CreatedNode.NewFields.HookSetTxnID; + var HookOn = node.CreatedNode.NewFields.HookOn; + if (HookOn == undefined) { HookOn = "0000000000000000000000000000000000000000000000000000000000000000" } + + var HooksRecord = temporaryHooksRecord[tx.Account]; + if (uriTokenRecord === undefined) { + temporaryHooksRecord[tx.Account] = { + HookNamespace: [ HookNamespace ], + HookHash: HookHash, + HookSetTxnID: HookSetTxnID, + HookOn: HookOn, + hookcount: 1, + hookinvokecount: 0, + hash: tx.hash, + date: xahauLedger.ledger.close_time + }; + } else { + temporaryHooksRecord[tx.Account].hookcount = HooksRecord.hookcount + 1; + temporaryHooksRecord[tx.Account].HookNameSpace.push( [ tx.HookNamespace ] ); + } + } + } + } + + } + + // (Hooks)Invoke detection + if (tx.TransactionType === "Invoke" && (tx.meta.TransactionResult === "tesSUCCESS" || tx.engine_result === "tesSUCCESS")) { + //this also need to be added with info from the correct place, looping through like Hookset etc + var HooksRecord = temporaryHooksRecord[tx.Account]; + if (uriTokenRecord === undefined) { + temporaryHooksRecord[tx.Account] = { + HookNamespace: [], + HookSetTxnID: [ tx.hash ], + hookcount: 0, + hookinvokecount: 1, + hash: tx.hash, + date: xahauLedger.ledger.close_time + }; + } else { + temporaryHooksRecord[tx.Account].hookinvokecount = HooksRecord.hookinvokecount + 1; + } + } + + } + + if (Object.keys(temporaryMintAccountRecord).length !== 0) { + for (const [key, value] of Object.entries(temporaryMintAccountRecord)) { + delete temporaryMintAccountRecord[key]; + await dbManager.RecordMintTx(key, value.amount, value.tx_count, value.date, value.newly_funded_account); + } + } + + if (Object.keys(temporaryURITokenRecord).length !== 0) { + for (const [key, value] of Object.entries(temporaryURITokenRecord)) { + // Log("INF", `processing temporaryURITokenRecord -> temp:${JSON.stringify(temporaryURITokenRecord)} mint:${value.uritokenmint}`); + delete temporaryURITokenRecord[key]; + await dbManager.RecordURIToken(key, value.totalmintcount, value.uritokenburn.count, value.totalbuycount, value.totalsellcount, value.date); + + // process Mint + if (value.totalmintcount > 0) { + //Log("INF", `processing uritokenmint before addURIToken -> no[]:${value.uritokenmint.uri} [0]:${value.uritokenmint.uri[0]} and [1]:${value.uritokenmint.uri[1]}`); + for (const item of value.uritokenmint) { + //Log("INF",`uritokenmint loop Count:${item.count} --> key:${key} URITokenID:${item.details.URITokenID} URI: ${item.details.URI}`); + await dbManager.db.URITokensAdd(key, item.details.URITokenID, item.details.URI, value.hash); + } + Log("INF", `processed ${value.totalmintcount} uritokenmint(s) successfully (URITokens Minted ${ value.uritokenmint.map(item => item.details.URITokenID) })`); + } + + // process Burn + if (value.uritokenburn.count > 0) { + // Log("INF",`processing ${value.uritokenburn.count}:uritokenburn -> URITokens:${value.uritokenburn.URITokenID} removed from ${key} successfully`); + await dbManager.db.URITokensRemove(key, value.uritokenburn.URITokenID); + Log("INF", `processed ${value.totalmintcount} uritokenburn(s) successfully (URITokens Burnt ${ value.uritokenburn.URITokenID })`); + } + + // process Buy + if (value.totalbuycount > 0) { + //Log("INF", `processing ${value.totalbuycount}:uritokenbuys ${JSON.stringify(value.uritokenbuy)}`); + for (const item of value.uritokenbuy) { + //Log("INF",`uritokenbuy loop Count:${item.count} --> key:${key} URITokenID:${JSON.stringify(item)} value:${JSON.stringify(value.uritokenbuy)}`); + await dbManager.db.URITokensAdd(key, item.details.URITokenID, item.details.URI, value.hash); + } + Log("INF", `processed ${value.totalbuycount}:uritokenbuys, successfully (sold by ${value.uritokenbuy.map(item => item.details.sellAccount)})`); + } + + // process Sell + if (value.totalsellcount > 0) { + //Log("INF", `processing ${value.uritokensell.totalCount}:uritokenbuys uritokensell:${JSON.stringify(value.uritokensell)}`); + for (const item of value.uritokensell) { + //Log("INF",`uritokensell loop Count:${item.count} --> key:${key} URITokenID:${item.details.URITokenID} URI:${item.details.URI}`); + await dbManager.db.URITokensRemove(key, item.details.URITokenID, item.details.URI, value.hash); + } + Log("INF", `processed ${value.totalsellcount}:uritokensells, successfully (brought by ${value.uritokensell.map(item => item.details.buyAccount)})`); + } + } + } + + if (Object.keys(temporaryHooksRecord).length !== 0) { + for (const [key, value] of Object.entries(temporaryHooksRecord)) { + await dbManager.RecordHooks(key, value.hookcount, value.hookinvokecount, value.date); + // Log("INF", `processing temporaryHooksRecord -> temp:${JSON.stringify(temporaryHooksRecord)} hooksrecord:${HooksRecord}`); + delete temporaryHooksRecord[key]; + await dbManager.db.RecordHookSet(key, value.HookNamespace, value.HookSetTxnID, value.HookOn, value.hash, value.date); + Log("INF", `processed ${value.hookcount}:hooksets ${value.hookinvokecount}:hookinvoke successfully (HookHash ${value.hash})`); + } + + } + + dbManager.db.UpdateMiscRecord("lastSyncedXahauLedgerIndex", ledger + ledgerID); + + } catch (err) { + Log("ERR", `Re-syncing Error (Xahau): ${err}`); + throw err; + } +} + async function StartXrplListener() { if (xrplListener) { Log("ERR", "xrplListener is enabled; Do not enable twice"); return false; @@ -61,17 +347,16 @@ async function StartXrplListener() { // XRPL xrplClient.on("transaction", async (tx) => { if (Object.keys(temporaryBurnAccountRecord).length > 0 && tx.ledger_index > temporaryLastSyncedXrplLedger) { - dbManager.UpdateMiscRecord("lastSyncedXrplLedgerIndex", temporaryLastSyncedXrplLedger); + for (const [key, value] of Object.entries(temporaryBurnAccountRecord)) { delete temporaryBurnAccountRecord[key]; - await record.RecordBurnTx(key, value.amount, value.tx_count, value.date); + await dbManager.RecordBurnTx(key, value.amount, value.tx_count, value.date); } - } - - if (tx.transaction.hasOwnProperty("OperationLimit") && !tx.transaction.hasOwnProperty("TicketSequence") && tx.transaction.OperationLimit === 21338) { - if (tx.engine_result === "tesSUCCESS" || tx.engine_result.substr(0, 3) === "tec") { - temporaryLastSyncedXrplLedger = tx.ledger_index; - + }; + if (tx.engine_result === "tesSUCCESS" || tx.engine_result.substr(0, 3) === "tec") { + dbManager.db.UpdateMiscRecord("lastSyncedXrplLedgerIndex", tx.ledger_index); + + if (tx.transaction.hasOwnProperty("OperationLimit") && !tx.transaction.hasOwnProperty("TicketSequence") && tx.transaction.OperationLimit === 21337) { var accountBurnRecord = temporaryBurnAccountRecord[tx.transaction.Account]; if (accountBurnRecord === undefined) { temporaryBurnAccountRecord[tx.transaction.Account] = { @@ -85,7 +370,7 @@ async function StartXrplListener() { } } } - }); + }) } async function StartXahauListener() { @@ -94,55 +379,24 @@ async function StartXahauListener() { } xahauListener = true; - const _subXahauTx = await xahauClient.send({ + const subXahauLedger = await xahauClient.send({ "command": "subscribe", "streams": ["transactions"] }); Log("INF", "Listening to Xahau for B2M (Mint) traffic"); - - // XAHAU + + // check for all the Transactions types, and then process them (adding any types in processXahauTransactions() needs to be added here too.) xahauClient.on("transaction", async (tx) => { - if (Object.keys(temporaryMintAccountRecord).length > 0 && tx.ledger_index > temporaryLastSyncedXahauLedger) { - dbManager.UpdateMiscRecord("lastSyncedXahauLedgerIndex", temporaryLastSyncedXahauLedger); - for (const [key, value] of Object.entries(temporaryMintAccountRecord)) { - delete temporaryMintAccountRecord[key]; - await record.RecordMintTx(key, value.amount, value.tx_count, value.date, value.newly_funded_account); - } - } - - if (tx.transaction.TransactionType === "Import" && tx.engine_result === "tesSUCCESS") { - var newlyFundedAccount = 0; - var import_amount = null; - temporaryLastSyncedXahauLedger = tx.ledger_index; - - tx.meta.AffectedNodes.forEach(metadata => { - if (metadata.hasOwnProperty("CreatedNode") && metadata.CreatedNode.LedgerEntryType === "AccountRoot") { - newlyFundedAccount = 1; - import_amount = parseInt(metadata.CreatedNode.NewFields.Balance); - } - if (metadata.hasOwnProperty("ModifiedNode") && metadata.ModifiedNode.LedgerEntryType === "AccountRoot") { - import_amount = (parseInt(metadata.ModifiedNode.FinalFields.Balance) + parseInt(tx.Fee)) - parseInt(metadata.ModifiedNode.PreviousFields.Balance ?? metadata.ModifiedNode.FinalFields.Balance); - } - }) - - var accountMintRecord = temporaryMintAccountRecord[tx.transaction.Account]; - if (accountMintRecord === undefined) { - temporaryMintAccountRecord[tx.transaction.Account] = { - amount: import_amount, - tx_count: 1, - date: tx.transaction.date, - newly_funded_account: newlyFundedAccount - }; - } else { - temporaryMintAccountRecord[tx.transaction.Account].amount = accountMintRecord.amount + import_amount; - temporaryMintAccountRecord[tx.transaction.Account].tx_count = accountMintRecord.tx_count + 1; - temporaryMintAccountRecord[tx.transaction.Account].newly_funded_account = newlyFundedAccount; - } + if ( tx.engine_result === "tesSUCCESS" ) { + if ( tx.transaction.TransactionType === "Import" || tx.transaction.TransactionType === "URITokenMint" || tx.transaction.TransactionType === "URITokenBurn" || tx.transaction.TransactionType === "URITokenBuy" || tx.transaction.TransactionType === "SetHook" || tx.transaction.TransactionType === "Invoke" ) { + processXahauTransactions(tx, tx.ledger_index, 0) + } else { dbManager.db.UpdateMiscRecord("lastSyncedXahauLedgerIndex", tx.ledger_index) } } - }); + }) } +// Main loop function async function main() { // Get the current ledger(s) on Xahau & XRPL const currentXrplLedgerIndex = await xrplClient.send({ @@ -151,15 +405,16 @@ async function main() { const currentXahauLedgerIndex = await xahauClient.send({ "command": "ledger_current" }); - + // Check the last indexed ledger - let syncedXrplLedgerIndex = await dbManager.GetMiscRecord("lastSyncedXrplLedgerIndex"); - let syncedXahauLedgerIndex = await dbManager.GetMiscRecord("lastSyncedXahauLedgerIndex"); - + let syncedXrplLedgerIndex = await dbManager.db.GetMiscRecord("lastSyncedXrplLedgerIndex"); + let syncedXahauLedgerIndex = await dbManager.db.GetMiscRecord("lastSyncedXahauLedgerIndex"); + //console.log(`${currentXrplLedgerIndex}:currentXRPL ${syncedXrplLedgerIndex}:lastXRPL || ${currentXahauLedgerIndex}:currentXAHL ${syncedXahauLedgerIndex}:lastXAHL ${ledgerBurn}:ledgerBurn`); if (syncedXahauLedgerIndex === undefined || syncedXrplLedgerIndex === undefined) { - // Populate these 2 variables with ledger indexes which contains the first burn & mint txs - dbManager.GenerateMiscRecord("lastSyncedXrplLedgerIndex", ledgerBurn); - dbManager.GenerateMiscRecord("lastSyncedXahauLedgerIndex", ledgerMint); + // Populate these 2 variables with ledger indexes from the .env file + console.log("generating MiscRecord..."); + dbManager.db.GenerateMiscRecord("lastSyncedXrplLedgerIndex", ledgerBurn); + dbManager.db.GenerateMiscRecord("lastSyncedXahauLedgerIndex", ledgerMint); } else { // If synced[]LedgerIndex is defined, then it means that we've indexed the ledger in the past and should continue from there. syncedXrplLedgerIndex = syncedXrplLedgerIndex.value; @@ -188,11 +443,13 @@ async function main() { // Because we're re-syncing with the ledger, we will re-sync until the Last Closed Ledger (essentially meaning that we're fully synced) while (ledgerBurn < currentXrplLedgerIndex.ledger_current_index || ledgerMint < currentXahauLedgerIndex.ledger_current_index) { + // Sync XRPL (Burn) if (!xrplListener) { - const progress = parseInt(xrplReqID / xrplDelta * 100); - if (progress !== burnSyncProgress && progress <= 100) process.stdout.write(`${progress}% synced with XRPL...\r`); burnSyncProgress = progress; + const progress = parseInt(xrplReqID / xrplDelta * 100); + if (progress !== burnSyncProgress && progress <= 100) console.log(`${progress}% synced with XRPL...\r`); burnSyncProgress = progress; + var xrplLedger = await xrplClient.send({ "id": xrplReqID, "command": "ledger", @@ -200,11 +457,11 @@ async function main() { "transactions": true, "expand": true }); - + if (xrplLedger.validated === true) { try { xrplLedger.ledger.transactions.forEach(async tx => { - if (tx.hasOwnProperty("OperationLimit") && !tx.hasOwnProperty("TicketSequence") && tx.OperationLimit === 21338) { + if (tx.hasOwnProperty("OperationLimit") && !tx.hasOwnProperty("TicketSequence") && tx.OperationLimit === 21337) { if (tx.metaData.TransactionResult === "tesSUCCESS" || tx.metaData.TransactionResult.substr(0, 3) === "tec") { var accountBurnRecord = temporaryBurnAccountRecord[tx.Account]; if (accountBurnRecord === undefined) { @@ -223,10 +480,10 @@ async function main() { for (const [key, value] of Object.entries(temporaryBurnAccountRecord)) { delete temporaryBurnAccountRecord[key]; - await record.RecordBurnTx(key, value.amount, value.tx_count, value.date); + await dbManager.RecordBurnTx(key, value.amount, value.tx_count, value.date); } - - dbManager.UpdateMiscRecord("lastSyncedXrplLedgerIndex", ledgerBurn + xrplReqID); + + dbManager.db.UpdateMiscRecord("lastSyncedXrplLedgerIndex", ledgerBurn + xrplReqID); xrplReqID++; } catch (err) { Log("ERR", `Re-syncing Error (XRPL): ${err}`); @@ -245,7 +502,7 @@ async function main() { // Sync Xahau (Mint) if (!xahauListener) { const progress = parseInt(xahauReqID / xahauDelta * 100); - if (progress !== mintSyncProgress && progress <= 100) process.stdout.write(`${progress}% synced with Xahau...\r`); mintSyncProgress = progress; + if (progress !== mintSyncProgress && progress <= 100) console.log(`${progress}% synced with Xahau...\r`); mintSyncProgress = progress; var xahauLedger = await xahauClient.send({ "id": xahauReqID, @@ -256,49 +513,11 @@ async function main() { }); if(xahauLedger.validated === true) { - try { - xahauLedger.ledger.transactions.forEach(async tx => { - if (tx.TransactionType === "Import" && tx.metaData.TransactionResult === "tesSUCCESS") { - var newlyFundedAccount = 0; - var import_amount = null; - - tx.metaData.AffectedNodes.forEach(metadata => { - if (metadata.hasOwnProperty("CreatedNode") && metadata.CreatedNode.LedgerEntryType === "AccountRoot") { - newlyFundedAccount = 1; - import_amount = parseInt(metadata.CreatedNode.NewFields.Balance); - } - if (metadata.hasOwnProperty("ModifiedNode") && metadata.ModifiedNode.LedgerEntryType === "AccountRoot") { - import_amount = (parseInt(metadata.ModifiedNode.FinalFields.Balance) + parseInt(tx.Fee)) - parseInt(metadata.ModifiedNode.PreviousFields.Balance ?? metadata.ModifiedNode.FinalFields.Balance); - } - }) - - var accountMintRecord = temporaryMintAccountRecord[tx.Account]; - if (accountMintRecord === undefined) { - temporaryMintAccountRecord[tx.Account] = { - amount: import_amount, - tx_count: 1, - date: xahauLedger.ledger.close_time, - newly_funded_account: newlyFundedAccount - }; - } else { - temporaryMintAccountRecord[tx.Account].amount = accountMintRecord.amount + import_amount; - temporaryMintAccountRecord[tx.Account].tx_count = accountMintRecord.tx_count + 1; - } - } - }); - - for (const [key, value] of Object.entries(temporaryMintAccountRecord)) { - delete temporaryMintAccountRecord[key]; - await record.RecordMintTx(key, value.amount, value.tx_count, value.date, value.newly_funded_account); - } - - dbManager.UpdateMiscRecord("lastSyncedXahauLedgerIndex", ledgerMint + xahauReqID); - xahauReqID++; - } catch (err) { - Log("ERR", `Re-syncing Error (Xahau): ${err}`); - break; - } - } else if (xahauLedger.error === "lgrNotFound" || xahauLedger.validated === false) { + processXahauTransactions(xahauLedger, ledgerMint, xahauReqID) + xahauReqID++; + } + + if (xahauLedger.error === "lgrNotFound" || xahauLedger.validated === false) { await StartXahauListener(); Log("INF", `Re-synced ${xahauReqID-1} ledgers on Xahau`); diff --git a/src/stats.js b/src/stats.js index 9d04ed2..e19f77f 100755 --- a/src/stats.js +++ b/src/stats.js @@ -1,10 +1,11 @@ -const dbManager = require("../db/manager.js"); +const dbManager = require("../db/manager"); const prompt = require("prompt-sync")({ sigint: true }); async function main() { - console.log(`${__filename}: Get Burn2Mint stats from your own local indexer!`); + console.log(`\n${__filename}: \nGet Burn2Mint stats from your own local indexer!`); - console.log(`Data points:\n1. All Account Records.\n2. Unique Account Record.\n3. Overall B2M Statistics.\n4. All History Records.\n5. Daily B2M Amount.\n6. Monthly B2M Amount.\n7. EXIT.`); + //console.log(`Data points:\n1. All Account Records.\n2. Unique Account Record.\n3. Overall B2M Statistics.\n4. All History Records.\n5. Daily B2M Amount.\n6. Monthly B2M Amount.\n7. EXIT.`); + console.log(`Data points:\n1. Overall B2M Statistics.\n2. Unique Account Record.\n3. All Account Records.\n4. All History Records.\n5. Daily B2M Amount.\n6. Monthly B2M Amount.\n7. EXIT.`); const requestNumber = parseInt(prompt("> Request Number: ")); @@ -13,55 +14,81 @@ async function main() { switch (requestNumber) { case 1: - // All Account Records - var data_AllAccountRecord = await dbManager.GetAllAccountRecord(); - console.log(data_AllAccountRecord); - break; - case 2: - // Unique Account Record - const accountAddress = prompt(">> Account Address: ") - var data_AccountRecord = await dbManager.GetAccountRecord(accountAddress); - console.log(data_AccountRecord); - break; - case 3: // Overall B2M statistics + // get the last indexed ledger, and monthly history record + var syncedXrplLedgerIndex = await dbManager.db.GetMiscRecord("lastSyncedXrplLedgerIndex"); + var syncedXahauLedgerIndex = await dbManager.db.GetMiscRecord("lastSyncedXahauLedgerIndex"); + var data_MonthlyHistoryRecord = await dbManager.db.GetAllHistoryRecord("monthly"); + // setup vars and then loop to get totals var burnt_amount = 0; var minted_amount = 0; var total_funded_accounts = 0; - var data_MonthlyHistoryRecord = await dbManager.GetAllHistoryRecord("monthly"); + var uritoken_mint_count = 0; + var uritoken_burn_count = 0; + var uritoken_buy_count = 0; + var uritoken_sell_count = 0; + var hook_count = 0; + var hookinvoke_count = 0; data_MonthlyHistoryRecord.forEach(record => { - burnt_amount += record.burnt_amount; - minted_amount += record.minted_amount; + burnt_amount += Number(record.burnt_amount); + minted_amount += Number(record.minted_amount); total_funded_accounts += record.newly_funded_account; + uritoken_mint_count += record.uritoken_mint_count; + uritoken_burn_count += record.uritoken_burn_count; + uritoken_buy_count += record.uritoken_buy_count; + uritoken_sell_count += record.uritoken_sell_count; + hook_count += record.hook_count; + hookinvoke_count += record.hookinvoke_count; }); - // To get the current B2M rate (XRP-to-XAH): Overall Minted Amount (XAH - including import tx fee) / Burnt Amount (XRP) - console.log(` -- B2M-Funded Account : ${total_funded_accounts} Xahau Accounts`); - console.log(` -- Burnt XRP : ${burnt_amount / 1000000} $XRP`); - console.log(` -- Minted XAH : ${minted_amount / 1000000} $XAH`); - console.log(` -- Un-minted XAH : ${(burnt_amount - minted_amount) / 1000000} $XAH`); - break; - case 4: + + // create response + + console.log(` -- lastSyncedXrplLedger : ${syncedXrplLedgerIndex.value}`); + console.log(` -- lastSyncedXahauLedger : ${syncedXahauLedgerIndex.value}`); + console.log(` -- B2M-Funded Account : ${total_funded_accounts} Xahau Accounts`); + console.log(` -- -- Burnt XRP : ${burnt_amount / 1000000} $XRP`); // Converted from drops + console.log(` -- -- Minted XAH : ${minted_amount / 1000000} $XAH`); + console.log(` -- -- Un-minted XAH : ${(burnt_amount - minted_amount) / 1000000} $XAH`); + console.log(` -- URIToken mint count : ${uritoken_mint_count}`); + console.log(` -- URIToken burn count : ${uritoken_burn_count}`); + console.log(` -- URIToken buy count : ${uritoken_buy_count}`); + console.log(` -- URIToken sell count : ${uritoken_sell_count}`); + console.log(` -- -- hook count : ${hook_count}`); + console.log(` -- hookinvoke count : ${hookinvoke_count}`); + main(); + case 2: + // All Account Records + var data_AllAccountRecord = await dbManager.db.GetAllAccountRecord(); + console.log(data_AllAccountRecord); + main(); + case 3: + // Unique Account Record + const accountAddress = prompt(">> Account Address: ") + var data_AccountRecord = await dbManager.db.GetAccountRecord(accountAddress); + console.log(data_AccountRecord); + main(); + case 4: // All History Records const tableNameAll = prompt(">> Table name (daily/monthly): "); - var data_AllHistoryRecord = await dbManager.GetAllHistoryRecord(tableNameAll); + var data_AllHistoryRecord = await dbManager.db.GetAllHistoryRecord(tableNameAll); console.log(data_AllHistoryRecord); - break; + main(); case 5: // Daily B2M amount const dailyDate = prompt(">> Date (YYY-MM-DD): "); - var data_DailyHistoryRecord = await dbManager.GetHistoryRecord("daily", dailyDate); + var data_DailyHistoryRecord = await dbManager.db.GetHistoryRecord("daily", dailyDate); console.log(data_DailyHistoryRecord); - break; + main(); case 6: // Monthly B2M amount const monthlyDate = prompt(">> Date (YYYY-MM): "); console.log(monthlyDate+"-00"); - var data_MonthlyHistoryRecord = await dbManager.GetHistoryRecord("monthly", monthlyDate+"-00"); + var data_MonthlyHistoryRecord = await dbManager.db.GetHistoryRecord("monthly", monthlyDate+"-00"); console.log(data_MonthlyHistoryRecord); - break; + main(); case 7: // exit - break; + process.exit(); } }