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();
}
}