Merge pull request #8 from ranchimall/main
This commit is contained in:
commit
d4f9aa7e82
90
README.md
90
README.md
@ -1,31 +1,81 @@
|
|||||||
# exchange-market
|
# Exchange-Market
|
||||||
Exchange market for FLO to rupee# and vise-versa
|
Exchange market for trading assets (FLO and tokens) using rupee#
|
||||||
|
|
||||||
## Run `commands`
|
## Installation
|
||||||
---------------
|
|
||||||
|
|
||||||
|
### Pre-requisite
|
||||||
|
- [X] Nodejs `version >= 12.9` (`--lts` recommended)
|
||||||
|
- [X] MySQL Server `version > 8.0`
|
||||||
|
|
||||||
|
### Download
|
||||||
|
Download the repository using git:
|
||||||
```
|
```
|
||||||
npm install - Install the app and node modules.
|
git clone https://github.com/ranchimall/exchange-market.git
|
||||||
npm run help - List all commands.
|
|
||||||
npm run setup - Finish the setup (configure and reset password).
|
|
||||||
npm run configure - Configure the app.
|
|
||||||
npm run reset-password - Reset the password (for private-key).
|
|
||||||
npm run create-schema - Create schema in MySQL database.
|
|
||||||
|
|
||||||
npm start - Start the application (main).
|
|
||||||
```
|
```
|
||||||
**NOTE:**
|
|
||||||
Argument `PASSWORD` required for `npm start`.
|
### Install
|
||||||
|
Install using npm:
|
||||||
|
```
|
||||||
|
cd exchange-market
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
Finish the configuration when prompted
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
#### General Configuration
|
||||||
|
If not finished during installation, or to re-configure use:
|
||||||
|
```
|
||||||
|
npm run configure
|
||||||
|
```
|
||||||
|
- **port**: Port of the server to run on
|
||||||
|
- **session secret**: A random session secret. (Enter `YES` to automatically randomize it)
|
||||||
|
|
||||||
|
- **MySQL host**: Host of the MySQL server (default: ***localhost***)
|
||||||
|
- **Database name**: Database in which the data should be stored (`<database-name>`) (default: ***exchange***)
|
||||||
|
- **MySQL username**: Username for MySQL (`<sql-username>`)
|
||||||
|
- **MySQL password**: Password for MySQL (`<sql-password>`)
|
||||||
|
|
||||||
|
***Recommended*** *(optional)* Create and use a MySQL user instead of root. Remember to give access to the database to the user.
|
||||||
|
|
||||||
|
#### Set/Reset Node key password
|
||||||
|
If not set during installation, or to reset password, use:
|
||||||
|
```
|
||||||
|
npm run reset-password
|
||||||
|
```
|
||||||
|
- **private key**: Private key of the node
|
||||||
|
- **password**: Password to set for the node (`<password>`)
|
||||||
|
|
||||||
|
**Note**: Private key of the node is encrypted using the `<password>`. Thus use a ***strong*** password.
|
||||||
|
|
||||||
|
### Create Database Schema (MySQL)
|
||||||
|
Create database schema in MySQL
|
||||||
|
```
|
||||||
|
CREATE DATABASE <database-name>;
|
||||||
|
USE <database-name>;
|
||||||
|
SOURCE args/schema.sql;
|
||||||
|
```
|
||||||
|
***Recommended*** *(optional)* Create a MySQL user and grant permissions
|
||||||
|
```
|
||||||
|
CREATE USER '<sql-username>'@'localhost' IDENTIFIED WITH mysql_native_password BY '<sql-password>';
|
||||||
|
GRANT ALL PRIVILEGES ON <database-name>.* TO '<sql-username>'@'localhost';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
```
|
||||||
|
|
||||||
|
### More
|
||||||
|
For help or list of all commands, use
|
||||||
|
```
|
||||||
|
npm run help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Starting the Server
|
||||||
|
After successful installation and configuration using the above steps, Exchange-Node can be started using:
|
||||||
```
|
```
|
||||||
npm start -- -PASSWORD=<password>
|
npm start -- -PASSWORD=<password>
|
||||||
```
|
```
|
||||||
*(Optional)*
|
|
||||||
Multiple instance can be run/setup on the same dir with different config files by using argument 'I'.
|
|
||||||
```
|
|
||||||
<command> -- -I=<instance_ID>
|
|
||||||
```
|
|
||||||
*(Optional)*
|
*(Optional)*
|
||||||
`console.debug` is now turned off by default. pass argument `--debug` to turn it on
|
`console.debug` is now turned off by default. pass argument `--debug` to turn it on
|
||||||
```
|
```
|
||||||
npm start -- -PASSWORD=<password> --debug
|
npm start -- -PASSWORD=<password> --debug
|
||||||
```
|
```
|
||||||
|
|||||||
56
debug/checksum-db.js
Normal file
56
debug/checksum-db.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
let _I = "";
|
||||||
|
for (let arg of process.argv)
|
||||||
|
if (/^-I=/.test(arg)) {
|
||||||
|
_I = arg.split(/=(.*)/s)[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DB = require('../src/database');
|
||||||
|
|
||||||
|
const ignoreTables = ['_backupCache', 'sinkShares'];
|
||||||
|
var ignoreTables_regex = new RegExp(ignoreTables.join("|"), "i");
|
||||||
|
function listTables() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
DB.query("SHOW TABLES").then(result => {
|
||||||
|
let tables = [];
|
||||||
|
for (let i in result)
|
||||||
|
for (let j in result[i])
|
||||||
|
if (!ignoreTables_regex.test(result[i][j]))
|
||||||
|
tables.push(result[i][j]);
|
||||||
|
resolve(tables);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function checksumTable(table) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
DB.query("CHECKSUM TABLE " + table).then(result => {
|
||||||
|
let checksum = result[0].Checksum;
|
||||||
|
DB.query("SELECT COUNT(*) AS rec_count FROM " + table)
|
||||||
|
.then(result => resolve({ table, rec_count: result[0].rec_count, checksum }))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function CheckDB() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const config = require(`../args/config${_I}.json`);
|
||||||
|
DB.connect(config["sql_user"], config["sql_pwd"], config["sql_db"], config["sql_host"]).then(pool => {
|
||||||
|
listTables().then(tables => {
|
||||||
|
Promise.allSettled(tables.map(t => checksumTable(t))).then(results => {
|
||||||
|
let records = results.filter(r => r.status === "fulfilled").map(r => r.value);
|
||||||
|
console.table(records);
|
||||||
|
let errors = results.filter(r => r.status === "rejected");
|
||||||
|
if (errors.length)
|
||||||
|
console.error(errors.map(r => r.reason));
|
||||||
|
resolve(true);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckDB().then(_ => process.exit(0)).catch(error => { console.error(error); process.exit(1); })
|
||||||
@ -71,24 +71,16 @@ a:focus-visible {
|
|||||||
box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1) inset;
|
box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1) inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.4rem 0.6rem;
|
|
||||||
border-radius: 0.3rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--accent-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
border: none;
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button,
|
||||||
|
.button {
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@ -98,39 +90,63 @@ button {
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 0.9rem;
|
font-size: inherit;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding: 0.8rem;
|
padding: 0.8rem;
|
||||||
border-radius: 0.3rem;
|
border-radius: 0.3rem;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
button:focus-visible {
|
button:focus-visible,
|
||||||
|
.button:focus-visible {
|
||||||
outline: var(--accent-color) solid medium;
|
outline: var(--accent-color) solid medium;
|
||||||
}
|
}
|
||||||
button:not(:disabled) {
|
button:not(:disabled),
|
||||||
|
.button:not(:disabled) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
color: var(--accent-color);
|
background-color: rgba(var(--text-color), 0.02);
|
||||||
background-color: rgba(var(--text-color), 0.06);
|
border: solid thin rgba(var(--text-color), 0.06);
|
||||||
}
|
|
||||||
.button--primary, .button--danger {
|
|
||||||
color: rgba(var(--background-color), 1) !important;
|
|
||||||
}
|
|
||||||
.button--primary .icon, .button--danger .icon {
|
|
||||||
fill: rgba(var(--background-color), 1);
|
|
||||||
}
|
}
|
||||||
.button--primary {
|
.button--primary {
|
||||||
|
color: rgba(var(--background-color), 1);
|
||||||
background-color: var(--accent-color);
|
background-color: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
.button--primary .icon {
|
||||||
|
fill: rgba(var(--background-color), 1);
|
||||||
|
}
|
||||||
|
.button--colored {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
.button--colored .icon {
|
||||||
|
fill: var(--accent-color);
|
||||||
|
}
|
||||||
.button--danger {
|
.button--danger {
|
||||||
background-color: var(--danger-color);
|
background-color: rgba(255, 115, 115, 0.062745098);
|
||||||
|
color: var(--danger-color);
|
||||||
|
}
|
||||||
|
.button--danger .icon {
|
||||||
|
fill: var(--danger-color);
|
||||||
}
|
}
|
||||||
.button--small {
|
.button--small {
|
||||||
padding: 0.4rem 0.6rem;
|
padding: 0.4rem 0.6rem;
|
||||||
}
|
}
|
||||||
|
.button--outlined {
|
||||||
|
border: solid rgba(var(--text-color), 0.3) 0.1rem;
|
||||||
|
background-color: rgba(var(--foreground-color), 1);
|
||||||
|
}
|
||||||
|
.button--transparent {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
filter: saturate(0);
|
||||||
|
}
|
||||||
|
|
||||||
.cta {
|
.cta {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@ -161,24 +177,6 @@ sm-input {
|
|||||||
--border-radius: 0.3rem;
|
--border-radius: 0.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
sm-button {
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.02em;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
--padding: 0.7rem 1rem;
|
|
||||||
}
|
|
||||||
sm-button[variant=primary] .icon {
|
|
||||||
fill: rgba(var(--background-color), 1);
|
|
||||||
}
|
|
||||||
sm-button[disabled] .icon {
|
|
||||||
fill: rgba(var(--text-color), 0.6);
|
|
||||||
}
|
|
||||||
sm-button.danger {
|
|
||||||
--background: var(--danger-color);
|
|
||||||
color: rgba(var(--background-color), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
sm-form {
|
sm-form {
|
||||||
--gap: 1rem;
|
--gap: 1rem;
|
||||||
}
|
}
|
||||||
@ -191,7 +189,7 @@ ul {
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide {
|
.hidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +205,6 @@ ul {
|
|||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
-ms-word-break: break-all;
|
-ms-word-break: break-all;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
-ms-hyphens: auto;
|
|
||||||
-webkit-hyphens: auto;
|
-webkit-hyphens: auto;
|
||||||
hyphens: auto;
|
hyphens: auto;
|
||||||
}
|
}
|
||||||
@ -362,10 +359,18 @@ ul {
|
|||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.margin-right-auto {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.margin-left-0-5 {
|
.margin-left-0-5 {
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.margin-left-auto {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.icon-only {
|
.icon-only {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
aspect-ratio: 1/1;
|
aspect-ratio: 1/1;
|
||||||
@ -415,23 +420,13 @@ ul {
|
|||||||
}
|
}
|
||||||
#confirmation_popup h4,
|
#confirmation_popup h4,
|
||||||
#prompt_popup h4 {
|
#prompt_popup h4 {
|
||||||
font-weight: 500;
|
font-size: 1.2rem;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 1rem;
|
||||||
}
|
|
||||||
#confirmation_popup sm-button,
|
|
||||||
#prompt_popup sm-button {
|
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
#confirmation_popup .flex,
|
#confirmation_popup .flex,
|
||||||
#prompt_popup .flex {
|
#prompt_popup .flex {
|
||||||
padding: 0;
|
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
#confirmation_popup .flex sm-button:first-of-type,
|
|
||||||
#prompt_popup .flex sm-button:first-of-type {
|
|
||||||
margin-right: 0.6rem;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#prompt_message {
|
#prompt_message {
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
@ -484,7 +479,6 @@ details summary {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
@ -499,33 +493,32 @@ details[open] summary .icon {
|
|||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
strip-select {
|
sm-chips {
|
||||||
--gap: 0;
|
--gap: 0;
|
||||||
background-color: rgba(var(--text-color), 0.06);
|
background-color: rgba(var(--text-color), 0.06);
|
||||||
border-radius: 0.2rem;
|
border-radius: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
strip-option {
|
sm-chip {
|
||||||
font-weight: 500;
|
position: relative;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
|
--border-radius: 0.5rem;
|
||||||
|
--padding: 0.5rem 0.8rem;
|
||||||
|
--background: rgba(var(--text-color), 0.06);
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
--border-radius: 0;
|
font-weight: 500;
|
||||||
--active-option-color: rgba(var(--background-color), 1);
|
|
||||||
--active-option-background-color: var(--accent-color);
|
|
||||||
}
|
}
|
||||||
strip-option:first-of-type {
|
sm-chip[selected] {
|
||||||
--border-radius: 0.2rem 0 0 0.2rem;
|
--background: var(--accent-color);
|
||||||
|
color: rgba(var(--background-color), 1);
|
||||||
}
|
}
|
||||||
strip-option:last-of-type {
|
sm-chip:first-of-type {
|
||||||
--border-radius: 0 0.2rem 0.2rem 0;
|
--border-radius: 0.3rem 0 0 0.3rem;
|
||||||
}
|
}
|
||||||
|
sm-chip:last-of-type {
|
||||||
sm-select,
|
--border-radius: 0 0.3rem 0.3rem 0;
|
||||||
sm-option {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sm-checkbox {
|
sm-checkbox {
|
||||||
@ -592,6 +585,15 @@ sm-checkbox {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#loading .rm-logo {
|
||||||
|
height: 3rem;
|
||||||
|
width: 3rem;
|
||||||
|
padding: 0.8rem;
|
||||||
|
background-color: rgba(var(--text-color), 0.06);
|
||||||
|
border-radius: 5rem;
|
||||||
|
justify-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
#landing {
|
#landing {
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
}
|
}
|
||||||
@ -627,17 +629,20 @@ sm-checkbox {
|
|||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.multi-state-button {
|
.loader-button-wrapper {
|
||||||
display: grid;
|
display: grid;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
.multi-state-button > * {
|
.loader-button-wrapper > * {
|
||||||
grid-area: 1/1/2/2;
|
grid-area: 1/1/2/2;
|
||||||
}
|
}
|
||||||
.multi-state-button button {
|
.loader-button-wrapper button {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
.loader-button-wrapper sm-spinner {
|
||||||
|
justify-self: center;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
#home {
|
#home {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -647,15 +652,28 @@ sm-checkbox {
|
|||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
#login_form {
|
#login_section {
|
||||||
width: min(24rem, 100%);
|
display: flex;
|
||||||
margin: 0 auto;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#login_form__priv_key {
|
#login_form__priv_key {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#flo_id_warning {
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
#flo_id_warning .icon {
|
||||||
|
height: 3rem;
|
||||||
|
width: 3rem;
|
||||||
|
padding: 0.8rem;
|
||||||
|
overflow: visible;
|
||||||
|
background-color: #ffc107;
|
||||||
|
border-radius: 3rem;
|
||||||
|
fill: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
#main_header {
|
#main_header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@ -724,7 +742,6 @@ sm-checkbox {
|
|||||||
transition: background-color 0.3s;
|
transition: background-color 0.3s;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
grid-template-columns: auto 1fr auto auto;
|
grid-template-columns: auto 1fr auto auto;
|
||||||
@ -869,7 +886,6 @@ sm-checkbox {
|
|||||||
outline: none;
|
outline: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
@ -1058,10 +1074,10 @@ sm-checkbox {
|
|||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
.live-order[data-type=buy] {
|
.live-order[data-type=buy] {
|
||||||
background-color: #00fa9a10;
|
background-color: rgba(0, 250, 154, 0.062745098);
|
||||||
}
|
}
|
||||||
.live-order[data-type=sell] {
|
.live-order[data-type=sell] {
|
||||||
background-color: #e2135110;
|
background-color: rgba(226, 19, 81, 0.062745098);
|
||||||
}
|
}
|
||||||
|
|
||||||
#portfolio {
|
#portfolio {
|
||||||
@ -1238,21 +1254,21 @@ sm-checkbox {
|
|||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
.personal-asset-balance:nth-of-type(1) {
|
.personal-asset-balance:nth-of-type(1) {
|
||||||
background: url("bg-art2.svg") no-repeat bottom right, #c2ffd7;
|
background: url("bg-art2.svg") no-repeat bottom right, hsl(141deg, 100%, 88%);
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
}
|
}
|
||||||
.personal-asset-balance:nth-of-type(1) .icon {
|
.personal-asset-balance:nth-of-type(1) .icon {
|
||||||
background-color: rgba(102, 255, 156, 0.5);
|
background-color: hsla(141deg, 100%, 70%, 0.5);
|
||||||
}
|
}
|
||||||
.personal-asset-balance:nth-of-type(1) .button {
|
.personal-asset-balance:nth-of-type(1) .button {
|
||||||
border: solid thin rgba(102, 255, 156, 0.5);
|
border: solid thin hsla(141deg, 100%, 70%, 0.5);
|
||||||
}
|
}
|
||||||
.personal-asset-balance:nth-of-type(2) {
|
.personal-asset-balance:nth-of-type(2) {
|
||||||
background: url("back.svg") no-repeat top right, #fcffa8;
|
background: url("back.svg") no-repeat top right, hsl(62deg, 100%, 83%);
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
}
|
}
|
||||||
.personal-asset-balance:nth-of-type(2) .icon {
|
.personal-asset-balance:nth-of-type(2) .icon {
|
||||||
background-color: rgba(255, 234, 0, 0.5);
|
background-color: hsla(55deg, 100%, 50%, 0.5);
|
||||||
}
|
}
|
||||||
.personal-asset-balance > .flex {
|
.personal-asset-balance > .flex {
|
||||||
margin-bottom: 0.3rem;
|
margin-bottom: 0.3rem;
|
||||||
@ -1348,32 +1364,6 @@ sm-checkbox {
|
|||||||
background-color: rgba(var(--text-color), 0.06);
|
background-color: rgba(var(--text-color), 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stateful-button-wrapper {
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.stateful-button-wrapper sm-button,
|
|
||||||
.stateful-button-wrapper slide-button {
|
|
||||||
width: 100%;
|
|
||||||
z-index: 1;
|
|
||||||
transition: -webkit-clip-path 0.3s;
|
|
||||||
transition: clip-path 0.3s;
|
|
||||||
transition: clip-path 0.3s, -webkit-clip-path 0.3s;
|
|
||||||
-webkit-clip-path: circle(100%);
|
|
||||||
clip-path: circle(100%);
|
|
||||||
}
|
|
||||||
.stateful-button-wrapper sm-button.clip,
|
|
||||||
.stateful-button-wrapper slide-button.clip {
|
|
||||||
pointer-events: none;
|
|
||||||
-webkit-clip-path: circle(0);
|
|
||||||
clip-path: circle(0);
|
|
||||||
}
|
|
||||||
.stateful-button-wrapper sm-spinner {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stateful-result {
|
.stateful-result {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -1474,18 +1464,12 @@ sm-checkbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 40rem) {
|
@media screen and (max-width: 40rem) {
|
||||||
sm-button {
|
|
||||||
--padding: 0.9rem 1.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#user_popup_button {
|
#user_popup_button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main_navbar__item--active .item__title {
|
.main_navbar__item--active .item__title {
|
||||||
transform: translateY(100%);
|
transform: translateY(100%);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@ -1493,42 +1477,36 @@ sm-checkbox {
|
|||||||
.main_navbar__item--active .icon {
|
.main_navbar__item--active .icon {
|
||||||
transform: translateY(50%) scale(1.2);
|
transform: translateY(50%) scale(1.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#asset_list_wrapper {
|
#asset_list_wrapper {
|
||||||
padding: 0 1.5rem;
|
padding: 0 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.listed-asset {
|
.listed-asset {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border-bottom: solid thin rgba(var(--text-color), 0.1);
|
border-bottom: solid thin rgba(var(--text-color), 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#exchange {
|
#exchange {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
#login_section {
|
||||||
#login_form {
|
|
||||||
padding: 0 1.5rem;
|
padding: 0 1.5rem;
|
||||||
}
|
}
|
||||||
|
#login_form {
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
#asset_page__header {
|
#asset_page__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 1rem 1.5rem 0.5rem 0.8rem;
|
padding: 1rem 1.5rem 0.5rem 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#chart_header {
|
#chart_header {
|
||||||
padding: 0 1.5rem 0.5rem 1.5rem;
|
padding: 0 1.5rem 0.5rem 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#trade_form {
|
#trade_form {
|
||||||
padding: 0 1.5rem;
|
padding: 0 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#price_chart_container {
|
#price_chart_container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#asset_page__footer button {
|
#asset_page__footer button {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -1550,7 +1528,6 @@ sm-checkbox {
|
|||||||
#asset_page__footer button.active .icon {
|
#asset_page__footer button.active .icon {
|
||||||
fill: var(--accent-color);
|
fill: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide-on-mobile {
|
.hide-on-mobile {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
@ -1559,53 +1536,43 @@ sm-checkbox {
|
|||||||
sm-popup {
|
sm-popup {
|
||||||
--width: 24rem;
|
--width: 24rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup__header {
|
.popup__header {
|
||||||
grid-column: 1/-1;
|
grid-column: 1/-1;
|
||||||
padding: 1rem 1.5rem 0 1.5rem;
|
padding: 1rem 1.5rem 0 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#confirmation_popup {
|
#confirmation_popup {
|
||||||
--width: 24rem;
|
--width: 24rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-layout {
|
.page-layout {
|
||||||
grid-template-columns: 1fr 90vw 1fr;
|
grid-template-columns: 1fr 90vw 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-page {
|
.mobile-page {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
background-color: rgba(var(--foreground-color), 1);
|
background-color: rgba(var(--foreground-color), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide-on-desktop {
|
.hide-on-desktop {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#home {
|
#home {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
grid-template-areas: "header header" "nav pages";
|
grid-template-areas: "header header" "nav pages";
|
||||||
}
|
}
|
||||||
|
|
||||||
#main_header {
|
#main_header {
|
||||||
grid-area: header;
|
grid-area: header;
|
||||||
}
|
}
|
||||||
|
|
||||||
#user_popup_button {
|
#user_popup_button {
|
||||||
justify-self: flex-end;
|
justify-self: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main_navbar {
|
#main_navbar {
|
||||||
grid-area: nav;
|
grid-area: nav;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main_navbar__item {
|
.main_navbar__item {
|
||||||
padding: 1.5rem 2rem 1.5rem 1rem;
|
padding: 1.5rem 2rem 1.5rem 1rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -1624,7 +1591,6 @@ sm-checkbox {
|
|||||||
.main_navbar__item--active {
|
.main_navbar__item--active {
|
||||||
background-color: rgba(var(--text-color), 0.06);
|
background-color: rgba(var(--text-color), 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pages_container {
|
#pages_container {
|
||||||
grid-area: pages;
|
grid-area: pages;
|
||||||
}
|
}
|
||||||
@ -1632,7 +1598,9 @@ sm-checkbox {
|
|||||||
margin: 0 1.5rem;
|
margin: 0 1.5rem;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
|
#login_section {
|
||||||
|
width: 24rem;
|
||||||
|
}
|
||||||
.is-signed-in #exchange {
|
.is-signed-in #exchange {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -1645,15 +1613,12 @@ sm-checkbox {
|
|||||||
grid-template-columns: 17rem minmax(0, 1fr);
|
grid-template-columns: 17rem minmax(0, 1fr);
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
#price_history_chart {
|
#price_history_chart {
|
||||||
height: 20rem;
|
height: 20rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#asset_list_wrapper {
|
#asset_list_wrapper {
|
||||||
grid-row: 1/3;
|
grid-row: 1/3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.listed-asset {
|
.listed-asset {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
@ -1669,16 +1634,13 @@ sm-checkbox {
|
|||||||
height: 2rem;
|
height: 2rem;
|
||||||
background-color: var(--accent-color);
|
background-color: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-card {
|
.order-card {
|
||||||
grid-template-columns: auto 1fr 1fr 1fr auto;
|
grid-template-columns: auto 1fr 1fr 1fr auto;
|
||||||
grid-template-areas: "checkbox quantity price amount time cancel";
|
grid-template-areas: "checkbox quantity price amount time cancel";
|
||||||
}
|
}
|
||||||
|
|
||||||
.orders_section__header {
|
.orders_section__header {
|
||||||
background-color: rgba(var(--foreground-color), 1);
|
background-color: rgba(var(--foreground-color), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#history .sticky,
|
#history .sticky,
|
||||||
#portfolio_asset_page .sticky {
|
#portfolio_asset_page .sticky {
|
||||||
background-color: rgba(var(--foreground-color), 1);
|
background-color: rgba(var(--foreground-color), 1);
|
||||||
@ -1701,7 +1663,6 @@ sm-checkbox {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
grid-template-columns: 18rem 1fr;
|
grid-template-columns: 18rem 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.completed-trade {
|
.completed-trade {
|
||||||
grid-template-columns: 1fr 1fr 1fr 8rem;
|
grid-template-columns: 1fr 1fr 1fr 8rem;
|
||||||
grid-template-areas: "quantity price amount info";
|
grid-template-areas: "quantity price amount info";
|
||||||
@ -1723,7 +1684,6 @@ sm-checkbox {
|
|||||||
#home {
|
#home {
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
#asset_page_wrapper {
|
#asset_page_wrapper {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
@ -1745,7 +1705,6 @@ sm-checkbox {
|
|||||||
width: 0.5rem;
|
width: 0.5rem;
|
||||||
height: 0.5rem;
|
height: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: rgba(var(--text-color), 0.3);
|
background: rgba(var(--text-color), 0.3);
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
@ -1753,7 +1712,6 @@ sm-checkbox {
|
|||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: rgba(var(--text-color), 0.5);
|
background: rgba(var(--text-color), 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-card .cancel-order {
|
.order-card .cancel-order {
|
||||||
justify-self: flex-end;
|
justify-self: flex-end;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
2
docs/css/main.min.css
vendored
2
docs/css/main.min.css
vendored
File diff suppressed because one or more lines are too long
@ -71,19 +71,13 @@ a {
|
|||||||
box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1) inset;
|
box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1) inset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
a.button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.4rem 0.6rem;
|
|
||||||
border-radius: 0.3rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--accent-color);
|
|
||||||
}
|
|
||||||
fieldset {
|
fieldset {
|
||||||
border: none;
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
button {
|
button,
|
||||||
|
.button {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@ -93,39 +87,65 @@ button {
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 0.9rem;
|
font-size: inherit;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding: 0.8rem;
|
padding: 0.8rem;
|
||||||
border-radius: 0.3rem;
|
border-radius: 0.3rem;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
outline: var(--accent-color) solid medium;
|
outline: var(--accent-color) solid medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(:disabled) {
|
&:not(:disabled) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
color: var(--accent-color);
|
background-color: rgba(var(--text-color), 0.02);
|
||||||
background-color: rgba(var(--text-color), 0.06);
|
border: solid thin rgba(var(--text-color), 0.06);
|
||||||
&--primary,
|
&--primary {
|
||||||
&--danger {
|
color: rgba(var(--background-color), 1);
|
||||||
color: rgba(var(--background-color), 1) !important;
|
background-color: var(--accent-color);
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
fill: rgba(var(--background-color), 1);
|
fill: rgba(var(--background-color), 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&--primary {
|
&--colored {
|
||||||
background-color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
|
.icon {
|
||||||
|
fill: var(--accent-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&--danger {
|
&--danger {
|
||||||
background-color: var(--danger-color);
|
background-color: #ff737310;
|
||||||
|
color: var(--danger-color);
|
||||||
|
.icon {
|
||||||
|
fill: var(--danger-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--small {
|
&--small {
|
||||||
padding: 0.4rem 0.6rem;
|
padding: 0.4rem 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--outlined {
|
||||||
|
border: solid rgba(var(--text-color), 0.3) 0.1rem;
|
||||||
|
background-color: rgba(var(--foreground-color), 1);
|
||||||
|
}
|
||||||
|
&--transparent {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
button:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
filter: saturate(0);
|
||||||
|
}
|
||||||
|
|
||||||
.cta {
|
.cta {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
@ -144,28 +164,6 @@ sm-input {
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
--border-radius: 0.3rem;
|
--border-radius: 0.3rem;
|
||||||
}
|
}
|
||||||
sm-button {
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.02em;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
--padding: 0.7rem 1rem;
|
|
||||||
&[variant="primary"] {
|
|
||||||
.icon {
|
|
||||||
fill: rgba(var(--background-color), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[disabled] {
|
|
||||||
.icon {
|
|
||||||
fill: rgba(var(--text-color), 0.6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.danger {
|
|
||||||
--background: var(--danger-color);
|
|
||||||
color: rgba(var(--background-color), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sm-form {
|
sm-form {
|
||||||
--gap: 1rem;
|
--gap: 1rem;
|
||||||
}
|
}
|
||||||
@ -176,7 +174,7 @@ ul {
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide {
|
.hidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,9 +343,15 @@ ul {
|
|||||||
.margin-right-0-5 {
|
.margin-right-0-5 {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
.margin-right-auto {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
.margin-left-0-5 {
|
.margin-left-0-5 {
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
.margin-left-auto {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
.icon-only {
|
.icon-only {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
aspect-ratio: 1/1;
|
aspect-ratio: 1/1;
|
||||||
@ -391,19 +395,12 @@ ul {
|
|||||||
#prompt_popup {
|
#prompt_popup {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
h4 {
|
h4 {
|
||||||
font-weight: 500;
|
font-size: 1.2rem;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 1rem;
|
||||||
}
|
|
||||||
sm-button {
|
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex {
|
.flex {
|
||||||
padding: 0;
|
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
sm-button:first-of-type {
|
|
||||||
margin-right: 0.6rem;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#prompt_message {
|
#prompt_message {
|
||||||
@ -471,29 +468,31 @@ details {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
strip-select {
|
sm-chips {
|
||||||
--gap: 0;
|
--gap: 0;
|
||||||
background-color: rgba(var(--text-color), 0.06);
|
background-color: rgba(var(--text-color), 0.06);
|
||||||
border-radius: 0.2rem;
|
border-radius: 0.2rem;
|
||||||
}
|
}
|
||||||
strip-option {
|
|
||||||
font-weight: 500;
|
sm-chip {
|
||||||
|
position: relative;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
|
--border-radius: 0.5rem;
|
||||||
|
--padding: 0.5rem 0.8rem;
|
||||||
|
--background: rgba(var(--text-color), 0.06);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
--border-radius: 0;
|
font-weight: 500;
|
||||||
--active-option-color: rgba(var(--background-color), 1);
|
&[selected] {
|
||||||
--active-option-background-color: var(--accent-color);
|
--background: var(--accent-color);
|
||||||
|
color: rgba(var(--background-color), 1);
|
||||||
|
}
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
--border-radius: 0.2rem 0 0 0.2rem;
|
--border-radius: 0.3rem 0 0 0.3rem;
|
||||||
}
|
}
|
||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
--border-radius: 0 0.2rem 0.2rem 0;
|
--border-radius: 0 0.3rem 0.3rem 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sm-select,
|
|
||||||
sm-option {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
sm-checkbox {
|
sm-checkbox {
|
||||||
--height: 1rem;
|
--height: 1rem;
|
||||||
--width: 1rem;
|
--width: 1rem;
|
||||||
@ -551,6 +550,16 @@ sm-checkbox {
|
|||||||
.mobile-page {
|
.mobile-page {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
#loading {
|
||||||
|
.rm-logo {
|
||||||
|
height: 3rem;
|
||||||
|
width: 3rem;
|
||||||
|
padding: 0.8rem;
|
||||||
|
background-color: rgba(var(--text-color), 0.06);
|
||||||
|
border-radius: 5rem;
|
||||||
|
justify-self: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
#landing {
|
#landing {
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
header {
|
header {
|
||||||
@ -585,16 +594,19 @@ sm-checkbox {
|
|||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.multi-state-button {
|
.loader-button-wrapper {
|
||||||
display: grid;
|
display: grid;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
align-items: center;
|
|
||||||
& > * {
|
& > * {
|
||||||
grid-area: 1/1/2/2;
|
grid-area: 1/1/2/2;
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
sm-spinner {
|
||||||
|
justify-self: center;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#home {
|
#home {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -603,13 +615,25 @@ sm-checkbox {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
}
|
}
|
||||||
#login_form {
|
#login_section {
|
||||||
width: min(24rem, 100%);
|
display: flex;
|
||||||
margin: 0 auto;
|
width: 100%;
|
||||||
}
|
}
|
||||||
#login_form__priv_key {
|
#login_form__priv_key {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
#flo_id_warning {
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
.icon {
|
||||||
|
height: 3rem;
|
||||||
|
width: 3rem;
|
||||||
|
padding: 0.8rem;
|
||||||
|
overflow: visible;
|
||||||
|
background-color: #ffc107;
|
||||||
|
border-radius: 3rem;
|
||||||
|
fill: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#main_header {
|
#main_header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -1236,26 +1260,6 @@ sm-checkbox {
|
|||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
background-color: rgba(var(--text-color), 0.06);
|
background-color: rgba(var(--text-color), 0.06);
|
||||||
}
|
}
|
||||||
.stateful-button-wrapper {
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
sm-button,
|
|
||||||
slide-button {
|
|
||||||
width: 100%;
|
|
||||||
z-index: 1;
|
|
||||||
transition: clip-path 0.3s;
|
|
||||||
clip-path: circle(100%);
|
|
||||||
&.clip {
|
|
||||||
pointer-events: none;
|
|
||||||
clip-path: circle(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sm-spinner {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.stateful-result {
|
.stateful-result {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -1340,15 +1344,9 @@ sm-checkbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 40rem) {
|
@media screen and (max-width: 40rem) {
|
||||||
sm-button {
|
|
||||||
--padding: 0.9rem 1.6rem;
|
|
||||||
}
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
#main_header {
|
|
||||||
// padding: 1.5rem;
|
|
||||||
}
|
|
||||||
#user_popup_button {
|
#user_popup_button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@ -1372,9 +1370,12 @@ sm-checkbox {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
#login_form {
|
#login_section {
|
||||||
padding: 0 1.5rem;
|
padding: 0 1.5rem;
|
||||||
}
|
}
|
||||||
|
#login_form {
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
#asset_page__header {
|
#asset_page__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 1rem 1.5rem 0.5rem 0.8rem;
|
padding: 1rem 1.5rem 0.5rem 0.8rem;
|
||||||
@ -1481,6 +1482,9 @@ sm-checkbox {
|
|||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#login_section {
|
||||||
|
width: 24rem;
|
||||||
|
}
|
||||||
.is-signed-in {
|
.is-signed-in {
|
||||||
#exchange {
|
#exchange {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
978
docs/index.html
978
docs/index.html
File diff suppressed because it is too large
Load Diff
@ -1,29 +1,40 @@
|
|||||||
(function (EXPORTS) { //btcOperator v1.0.13c
|
(function (EXPORTS) { //btcOperator v1.1.1
|
||||||
/* BTC Crypto and API Operator */
|
/* BTC Crypto and API Operator */
|
||||||
const btcOperator = EXPORTS;
|
const btcOperator = EXPORTS;
|
||||||
|
|
||||||
//This library uses API provided by chain.so (https://chain.so/)
|
//This library uses API provided by chain.so (https://chain.so/)
|
||||||
const URL = "https://chain.so/api/v2/";
|
const URL = "https://blockchain.info/";
|
||||||
|
|
||||||
const fetch_api = btcOperator.fetch = function (api) {
|
const fetch_api = btcOperator.fetch = function (api, json_res = true) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
console.debug(URL + api);
|
console.debug(URL + api);
|
||||||
fetch(URL + api).then(response => {
|
fetch(URL + api).then(response => {
|
||||||
response.json()
|
if (response.ok) {
|
||||||
.then(result => result.status === "success" ? resolve(result) : reject(result))
|
(json_res ? response.json() : response.text())
|
||||||
.catch(error => reject(error))
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
} else {
|
||||||
|
response.json()
|
||||||
|
.then(result => reject(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
}
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
const SATOSHI_IN_BTC = 1e8;
|
const SATOSHI_IN_BTC = 1e8;
|
||||||
|
|
||||||
|
const util = btcOperator.util = {};
|
||||||
|
|
||||||
|
util.Sat_to_BTC = value => parseFloat((value / SATOSHI_IN_BTC).toFixed(8));
|
||||||
|
util.BTC_to_Sat = value => parseInt(value * SATOSHI_IN_BTC);
|
||||||
|
|
||||||
function get_fee_rate() {
|
function get_fee_rate() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fetch('https://api.blockchain.info/mempool/fees').then(response => {
|
fetch('https://api.blockchain.info/mempool/fees').then(response => {
|
||||||
if (response.ok)
|
if (response.ok)
|
||||||
response.json()
|
response.json()
|
||||||
.then(result => resolve(parseFloat((result.regular / SATOSHI_IN_BTC).toFixed(8))))
|
.then(result => resolve(util.Sat_to_BTC(result.regular)))
|
||||||
.catch(error => reject(error));
|
.catch(error => reject(error));
|
||||||
else
|
else
|
||||||
reject(response);
|
reject(response);
|
||||||
@ -102,18 +113,21 @@
|
|||||||
if (!addr)
|
if (!addr)
|
||||||
return undefined;
|
return undefined;
|
||||||
let type = coinjs.addressDecode(addr).type;
|
let type = coinjs.addressDecode(addr).type;
|
||||||
if (["standard", "multisig", "bech32"].includes(type))
|
if (["standard", "multisig", "bech32", "multisigBech32"].includes(type))
|
||||||
return type;
|
return type;
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
btcOperator.multiSigAddress = function (pubKeys, minRequired) {
|
btcOperator.multiSigAddress = function (pubKeys, minRequired, bech32 = true) {
|
||||||
if (!Array.isArray(pubKeys))
|
if (!Array.isArray(pubKeys))
|
||||||
throw "pubKeys must be an array of public keys";
|
throw "pubKeys must be an array of public keys";
|
||||||
else if (pubKeys.length < minRequired)
|
else if (pubKeys.length < minRequired)
|
||||||
throw "minimum required should be less than the number of pubKeys";
|
throw "minimum required should be less than the number of pubKeys";
|
||||||
return coinjs.pubkeys2MultisigAddress(pubKeys, minRequired);
|
if (bech32)
|
||||||
|
return coinjs.pubkeys2MultisigAddressBech32(pubKeys, minRequired);
|
||||||
|
else
|
||||||
|
return coinjs.pubkeys2MultisigAddress(pubKeys, minRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
//convert from one blockchain to another blockchain (target version)
|
//convert from one blockchain to another blockchain (target version)
|
||||||
@ -213,8 +227,8 @@
|
|||||||
//BTC blockchain APIs
|
//BTC blockchain APIs
|
||||||
|
|
||||||
btcOperator.getBalance = addr => new Promise((resolve, reject) => {
|
btcOperator.getBalance = addr => new Promise((resolve, reject) => {
|
||||||
fetch_api(`get_address_balance/BTC/${addr}`)
|
fetch_api(`q/addressbalance/${addr}`)
|
||||||
.then(result => resolve(parseFloat(result.data.confirmed_balance)))
|
.then(result => resolve(util.Sat_to_BTC(result)))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -222,11 +236,13 @@
|
|||||||
BASE_INPUT_SIZE = 41,
|
BASE_INPUT_SIZE = 41,
|
||||||
LEGACY_INPUT_SIZE = 107,
|
LEGACY_INPUT_SIZE = 107,
|
||||||
BECH32_INPUT_SIZE = 27,
|
BECH32_INPUT_SIZE = 27,
|
||||||
|
BECH32_MULTISIG_INPUT_SIZE = 35,
|
||||||
SEGWIT_INPUT_SIZE = 59,
|
SEGWIT_INPUT_SIZE = 59,
|
||||||
MULTISIG_INPUT_SIZE_ES = 351,
|
MULTISIG_INPUT_SIZE_ES = 351,
|
||||||
BASE_OUTPUT_SIZE = 9,
|
BASE_OUTPUT_SIZE = 9,
|
||||||
LEGACY_OUTPUT_SIZE = 25,
|
LEGACY_OUTPUT_SIZE = 25,
|
||||||
BECH32_OUTPUT_SIZE = 23,
|
BECH32_OUTPUT_SIZE = 23,
|
||||||
|
BECH32_MULTISIG_OUTPUT_SIZE = 34,
|
||||||
SEGWIT_OUTPUT_SIZE = 23;
|
SEGWIT_OUTPUT_SIZE = 23;
|
||||||
|
|
||||||
function _redeemScript(addr, key) {
|
function _redeemScript(addr, key) {
|
||||||
@ -249,6 +265,8 @@
|
|||||||
return BASE_INPUT_SIZE + LEGACY_INPUT_SIZE;
|
return BASE_INPUT_SIZE + LEGACY_INPUT_SIZE;
|
||||||
case "bech32":
|
case "bech32":
|
||||||
return BASE_INPUT_SIZE + BECH32_INPUT_SIZE;
|
return BASE_INPUT_SIZE + BECH32_INPUT_SIZE;
|
||||||
|
case "multisigBech32":
|
||||||
|
return BASE_INPUT_SIZE + BECH32_MULTISIG_INPUT_SIZE;
|
||||||
case "multisig":
|
case "multisig":
|
||||||
switch (coinjs.script().decodeRedeemScript(rs).type) {
|
switch (coinjs.script().decodeRedeemScript(rs).type) {
|
||||||
case "segwit__":
|
case "segwit__":
|
||||||
@ -269,6 +287,8 @@
|
|||||||
return BASE_OUTPUT_SIZE + LEGACY_OUTPUT_SIZE;
|
return BASE_OUTPUT_SIZE + LEGACY_OUTPUT_SIZE;
|
||||||
case "bech32":
|
case "bech32":
|
||||||
return BASE_OUTPUT_SIZE + BECH32_OUTPUT_SIZE;
|
return BASE_OUTPUT_SIZE + BECH32_OUTPUT_SIZE;
|
||||||
|
case "multisigBech32":
|
||||||
|
return BASE_OUTPUT_SIZE + BECH32_MULTISIG_OUTPUT_SIZE;
|
||||||
case "multisig":
|
case "multisig":
|
||||||
return BASE_OUTPUT_SIZE + SEGWIT_OUTPUT_SIZE;
|
return BASE_OUTPUT_SIZE + SEGWIT_OUTPUT_SIZE;
|
||||||
default:
|
default:
|
||||||
@ -299,7 +319,7 @@
|
|||||||
parameters.privkeys[i] = coinjs.privkey2wif(key);
|
parameters.privkeys[i] = coinjs.privkey2wif(key);
|
||||||
});
|
});
|
||||||
if (invalids.length)
|
if (invalids.length)
|
||||||
throw "Invalid keys:" + invalids;
|
throw "Invalid private key for address:" + invalids;
|
||||||
}
|
}
|
||||||
//receiver-ids (and change-id)
|
//receiver-ids (and change-id)
|
||||||
if (!Array.isArray(parameters.receivers))
|
if (!Array.isArray(parameters.receivers))
|
||||||
@ -329,10 +349,10 @@
|
|||||||
const tx = coinjs.transaction();
|
const tx = coinjs.transaction();
|
||||||
let output_size = addOutputs(tx, receivers, amounts, change_address);
|
let output_size = addOutputs(tx, receivers, amounts, change_address);
|
||||||
addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver).then(result => {
|
addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver).then(result => {
|
||||||
if (result.change_amount > 0) //add change amount if any
|
if (result.change_amount > 0 && result.change_amount > result.fee) //add change amount if any (ignore dust change)
|
||||||
tx.outs[tx.outs.length - 1].value = parseInt(result.change_amount * SATOSHI_IN_BTC); //values are in satoshi
|
tx.outs[tx.outs.length - 1].value = util.BTC_to_Sat(result.change_amount); //values are in satoshi
|
||||||
if (fee_from_receiver) { //deduce fee from receivers if fee_from_receiver
|
if (fee_from_receiver) { //deduce fee from receivers if fee_from_receiver
|
||||||
let fee_remaining = parseInt(result.fee * SATOSHI_IN_BTC);
|
let fee_remaining = util.BTC_to_Sat(result.fee);
|
||||||
for (let i = 0; i < tx.outs.length - 1 && fee_remaining > 0; i++) {
|
for (let i = 0; i < tx.outs.length - 1 && fee_remaining > 0; i++) {
|
||||||
if (fee_remaining < tx.outs[i].value) {
|
if (fee_remaining < tx.outs[i].value) {
|
||||||
tx.outs[i].value -= fee_remaining;
|
tx.outs[i].value -= fee_remaining;
|
||||||
@ -398,30 +418,31 @@
|
|||||||
return reject("Insufficient Balance");
|
return reject("Insufficient Balance");
|
||||||
let addr = senders[rec_args.n],
|
let addr = senders[rec_args.n],
|
||||||
rs = redeemScripts[rec_args.n];
|
rs = redeemScripts[rec_args.n];
|
||||||
|
let addr_type = coinjs.addressDecode(addr).type;
|
||||||
let size_per_input = _sizePerInput(addr, rs);
|
let size_per_input = _sizePerInput(addr, rs);
|
||||||
fetch_api(`get_tx_unspent/BTC/${addr}`).then(result => {
|
fetch_api(`unspent?active=${addr}`).then(result => {
|
||||||
let utxos = result.data.txs;
|
let utxos = result.unspent_outputs;
|
||||||
console.debug("add-utxo", addr, rs, required_amount, utxos);
|
console.debug("add-utxo", addr, rs, required_amount, utxos);
|
||||||
for (let i = 0; i < utxos.length && required_amount > 0; i++) {
|
for (let i = 0; i < utxos.length && required_amount > 0; i++) {
|
||||||
if (!utxos[i].confirmations) //ignore unconfirmed utxo
|
if (!utxos[i].confirmations) //ignore unconfirmed utxo
|
||||||
continue;
|
continue;
|
||||||
var script;
|
var script;
|
||||||
if (!rs || !rs.length) //legacy script
|
if (!rs || !rs.length) //legacy script
|
||||||
script = utxos[i].script_hex;
|
script = utxos[i].script;
|
||||||
else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi))) {
|
else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi)) || addr_type === 'multisigBech32') {
|
||||||
//redeemScript for segwit/bech32
|
//redeemScript for segwit/bech32 and multisig (bech32)
|
||||||
let s = coinjs.script();
|
let s = coinjs.script();
|
||||||
s.writeBytes(Crypto.util.hexToBytes(rs));
|
s.writeBytes(Crypto.util.hexToBytes(rs));
|
||||||
s.writeOp(0);
|
s.writeOp(0);
|
||||||
s.writeBytes(coinjs.numToBytes((utxos[i].value * SATOSHI_IN_BTC).toFixed(0), 8));
|
s.writeBytes(coinjs.numToBytes(utxos[i].value.toFixed(0), 8));
|
||||||
script = Crypto.util.bytesToHex(s.buffer);
|
script = Crypto.util.bytesToHex(s.buffer);
|
||||||
} else //redeemScript for multisig
|
} else //redeemScript for multisig (segwit)
|
||||||
script = rs;
|
script = rs;
|
||||||
tx.addinput(utxos[i].txid, utxos[i].output_no, script, 0xfffffffd /*sequence*/); //0xfffffffd for Replace-by-fee
|
tx.addinput(utxos[i].tx_hash_big_endian, utxos[i].tx_output_n, script, 0xfffffffd /*sequence*/); //0xfffffffd for Replace-by-fee
|
||||||
//update track values
|
//update track values
|
||||||
rec_args.input_size += size_per_input;
|
rec_args.input_size += size_per_input;
|
||||||
rec_args.input_amount += parseFloat(utxos[i].value);
|
rec_args.input_amount += util.Sat_to_BTC(utxos[i].value);
|
||||||
required_amount -= parseFloat(utxos[i].value);
|
required_amount -= util.Sat_to_BTC(utxos[i].value);
|
||||||
if (fee_rate) //automatic fee calculation (dynamic)
|
if (fee_rate) //automatic fee calculation (dynamic)
|
||||||
required_amount += size_per_input * fee_rate;
|
required_amount += size_per_input * fee_rate;
|
||||||
}
|
}
|
||||||
@ -563,11 +584,14 @@
|
|||||||
btcOperator.createMultiSigTx = function (sender, redeemScript, receivers, amounts, fee = null, options = {}) {
|
btcOperator.createMultiSigTx = function (sender, redeemScript, receivers, amounts, fee = null, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
//validate tx parameters
|
//validate tx parameters
|
||||||
if (validateAddress(sender) !== "multisig")
|
let addr_type = validateAddress(sender);
|
||||||
|
if (!(["multisig", "multisigBech32"].includes(addr_type)))
|
||||||
return reject("Invalid sender (multisig):" + sender);
|
return reject("Invalid sender (multisig):" + sender);
|
||||||
else {
|
else {
|
||||||
let script = coinjs.script();
|
let script = coinjs.script();
|
||||||
let decode = script.decodeRedeemScript(redeemScript);
|
let decode = (addr_type == "multisig") ?
|
||||||
|
script.decodeRedeemScript(redeemScript) :
|
||||||
|
script.decodeRedeemScriptBech32(redeemScript);
|
||||||
if (!decode || decode.address !== sender)
|
if (!decode || decode.address !== sender)
|
||||||
return reject("Invalid redeem-script");
|
return reject("Invalid redeem-script");
|
||||||
}
|
}
|
||||||
@ -622,10 +646,10 @@
|
|||||||
let n = [];
|
let n = [];
|
||||||
for (let i in tx.ins) {
|
for (let i in tx.ins) {
|
||||||
var s = tx.extractScriptKey(i);
|
var s = tx.extractScriptKey(i);
|
||||||
if (s['type'] !== 'multisig')
|
if (s['type'] !== 'multisig' && s['type'] !== 'multisig_bech32')
|
||||||
n.push(s.signed == 'true' || (tx.witness[i] && tx.witness[i].length == 2))
|
n.push(s.signed == 'true' || (tx.witness[i] && tx.witness[i].length == 2))
|
||||||
else {
|
else {
|
||||||
var rs = coinjs.script().decodeRedeemScript(s.script);
|
var rs = coinjs.script().decodeRedeemScript(s.script); //will work for bech32 too, as only address is diff
|
||||||
let x = {
|
let x = {
|
||||||
s: s['signatures'],
|
s: s['signatures'],
|
||||||
r: rs['signaturesRequired'],
|
r: rs['signaturesRequired'],
|
||||||
@ -657,8 +681,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getTxOutput = (txid, i) => new Promise((resolve, reject) => {
|
const getTxOutput = (txid, i) => new Promise((resolve, reject) => {
|
||||||
fetch_api(`get_tx_outputs/BTC/${txid}/${i}`)
|
fetch_api(`rawtx/${txid}`)
|
||||||
.then(result => resolve(result.data.outputs))
|
.then(result => resolve(result.out[i]))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -672,8 +696,8 @@
|
|||||||
promises.push(getTxOutput(tx.ins[i].outpoint.hash, tx.ins[i].outpoint.index));
|
promises.push(getTxOutput(tx.ins[i].outpoint.hash, tx.ins[i].outpoint.index));
|
||||||
Promise.all(promises).then(inputs => {
|
Promise.all(promises).then(inputs => {
|
||||||
result.inputs = inputs.map(inp => Object({
|
result.inputs = inputs.map(inp => Object({
|
||||||
address: inp.address,
|
address: inp.addr,
|
||||||
value: parseFloat(inp.value)
|
value: util.Sat_to_BTC(inp.value)
|
||||||
}));
|
}));
|
||||||
let signed = checkSigned(tx, false);
|
let signed = checkSigned(tx, false);
|
||||||
result.inputs.forEach((inp, i) => inp.signed = signed[i]);
|
result.inputs.forEach((inp, i) => inp.signed = signed[i]);
|
||||||
@ -681,10 +705,10 @@
|
|||||||
result.outputs = tx.outs.map(out => {
|
result.outputs = tx.outs.map(out => {
|
||||||
var address;
|
var address;
|
||||||
switch (out.script.chunks[0]) {
|
switch (out.script.chunks[0]) {
|
||||||
case 0: //bech32
|
case 0: //bech32, multisig-bech32
|
||||||
address = encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.bech32.version, coinjs.bech32.hrp);
|
address = encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.bech32.version, coinjs.bech32.hrp);
|
||||||
break;
|
break;
|
||||||
case 169: //multisig, segwit
|
case 169: //segwit, multisig-segwit
|
||||||
address = encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.multisig);
|
address = encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.multisig);
|
||||||
break;
|
break;
|
||||||
case 118: //legacy
|
case 118: //legacy
|
||||||
@ -692,7 +716,7 @@
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
address,
|
address,
|
||||||
value: parseFloat(out.value / SATOSHI_IN_BTC)
|
value: util.Sat_to_BTC(out.value)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
//Parse Totals
|
//Parse Totals
|
||||||
@ -713,22 +737,115 @@
|
|||||||
return Crypto.util.bytesToHex(txid);
|
return Crypto.util.bytesToHex(txid);
|
||||||
}
|
}
|
||||||
|
|
||||||
btcOperator.getTx = txid => new Promise((resolve, reject) => {
|
const getLatestBlock = btcOperator.getLatestBlock = () => new Promise((resolve, reject) => {
|
||||||
fetch_api(`get_tx/BTC/${txid}`)
|
fetch_api(`q/getblockcount`)
|
||||||
.then(result => resolve(result.data))
|
.then(result => resolve(result))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
|
||||||
|
btcOperator.getTx = txid => new Promise((resolve, reject) => {
|
||||||
|
fetch_api(`rawtx/${txid}`).then(result => {
|
||||||
|
getLatestBlock().then(latest_block => resolve({
|
||||||
|
block: result.block_height,
|
||||||
|
txid: result.hash,
|
||||||
|
time: result.time * 1000,
|
||||||
|
confirmations: result.block_height === null ? 0 : latest_block - result.block_height, //calculate confirmations using latest block number as api doesnt relay it
|
||||||
|
size: result.size,
|
||||||
|
fee: util.Sat_to_BTC(result.fee),
|
||||||
|
inputs: result.inputs.map(i => Object({ address: i.prev_out.addr, value: util.Sat_to_BTC(i.prev_out.value) })),
|
||||||
|
total_input_value: util.Sat_to_BTC(result.inputs.reduce((a, i) => a + i.prev_out.value, 0)),
|
||||||
|
outputs: result.out.map(o => Object({ address: o.addr, value: util.Sat_to_BTC(o.value) })),
|
||||||
|
total_output_value: util.Sat_to_BTC(result.out.reduce((a, o) => a += o.value, 0)),
|
||||||
|
}))
|
||||||
|
}).catch(error => reject(error))
|
||||||
});
|
});
|
||||||
|
|
||||||
btcOperator.getAddressData = addr => new Promise((resolve, reject) => {
|
btcOperator.getTx.hex = txid => new Promise((resolve, reject) => {
|
||||||
fetch_api(`address/BTC/${addr}`)
|
fetch_api(`rawtx/${txid}?format=hex`, false)
|
||||||
.then(result => resolve(result.data))
|
.then(result => resolve(result))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
|
||||||
|
btcOperator.getAddressData = address => new Promise((resolve, reject) => {
|
||||||
|
fetch_api(`rawaddr/${address}`).then(data => {
|
||||||
|
let details = {};
|
||||||
|
details.balance = util.Sat_to_BTC(data.final_balance);
|
||||||
|
details.address = data.address;
|
||||||
|
details.txs = data.txs.map(tx => {
|
||||||
|
let d = {
|
||||||
|
txid: tx.hash,
|
||||||
|
time: tx.time * 1000, //s to ms
|
||||||
|
block: tx.block_height,
|
||||||
|
}
|
||||||
|
//sender list
|
||||||
|
d.tx_senders = {};
|
||||||
|
tx.inputs.forEach(i => {
|
||||||
|
if (i.prev_out.addr in d.tx_senders)
|
||||||
|
d.tx_senders[i.prev_out.addr] += i.prev_out.value;
|
||||||
|
else d.tx_senders[i.prev_out.addr] = i.prev_out.value;
|
||||||
|
});
|
||||||
|
d.tx_input_value = 0;
|
||||||
|
for (let s in d.tx_senders) {
|
||||||
|
let val = d.tx_senders[s];
|
||||||
|
d.tx_senders[s] = util.Sat_to_BTC(val);
|
||||||
|
d.tx_input_value += val;
|
||||||
|
}
|
||||||
|
d.tx_input_value = util.Sat_to_BTC(d.tx_input_value);
|
||||||
|
//receiver list
|
||||||
|
d.tx_receivers = {};
|
||||||
|
tx.out.forEach(o => {
|
||||||
|
if (o.addr in d.tx_receivers)
|
||||||
|
d.tx_receivers[o.addr] += o.value;
|
||||||
|
else d.tx_receivers[o.addr] = o.value;
|
||||||
|
});
|
||||||
|
d.tx_output_value = 0;
|
||||||
|
for (let r in d.tx_receivers) {
|
||||||
|
let val = d.tx_receivers[r];
|
||||||
|
d.tx_receivers[r] = util.Sat_to_BTC(val);
|
||||||
|
d.tx_output_value += val;
|
||||||
|
}
|
||||||
|
d.tx_output_value = util.Sat_to_BTC(d.tx_output_value);
|
||||||
|
d.tx_fee = util.Sat_to_BTC(tx.fee);
|
||||||
|
//tx type
|
||||||
|
if (tx.result > 0) { //net > 0, balance inc => type=in
|
||||||
|
d.type = "in";
|
||||||
|
d.amount = util.Sat_to_BTC(tx.result);
|
||||||
|
d.sender = Object.keys(d.tx_senders).filter(s => s !== address);
|
||||||
|
} else if (Object.keys(d.tx_receivers).some(r => r !== address)) { //net < 0, balance dec & receiver present => type=out
|
||||||
|
d.type = "out";
|
||||||
|
d.amount = util.Sat_to_BTC(tx.result * -1);
|
||||||
|
d.receiver = Object.keys(d.tx_receivers).filter(r => r !== address);
|
||||||
|
d.fee = d.tx_fee;
|
||||||
|
} else { //net < 0 (fee) & no other id in receiver list => type=self
|
||||||
|
d.type = "self";
|
||||||
|
d.amount = d.tx_receivers[address];
|
||||||
|
d.address = address
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
})
|
||||||
|
resolve(details);
|
||||||
|
}).catch(error => reject(error))
|
||||||
});
|
});
|
||||||
|
|
||||||
btcOperator.getBlock = block => new Promise((resolve, reject) => {
|
btcOperator.getBlock = block => new Promise((resolve, reject) => {
|
||||||
fetch_api(`get_block/BTC/${block}`)
|
fetch_api(`rawblock/${block}`).then(result => resolve({
|
||||||
.then(result => resolve(result.data))
|
height: result.height,
|
||||||
.catch(error => reject(error))
|
hash: result.hash,
|
||||||
|
merkle_root: result.mrkl_root,
|
||||||
|
prev_block: result.prev_block,
|
||||||
|
next_block: result.next_block[0],
|
||||||
|
size: result.size,
|
||||||
|
time: result.time * 1000, //s to ms
|
||||||
|
txs: result.tx.map(t => Object({
|
||||||
|
fee: t.fee,
|
||||||
|
size: t.size,
|
||||||
|
inputs: t.inputs.map(i => Object({ address: i.prev_out.addr, value: util.Sat_to_BTC(i.prev_out.value) })),
|
||||||
|
total_input_value: util.Sat_to_BTC(t.inputs.reduce((a, i) => a + i.prev_out.value, 0)),
|
||||||
|
outputs: t.out.map(o => Object({ address: o.addr, value: util.Sat_to_BTC(o.value) })),
|
||||||
|
total_output_value: util.Sat_to_BTC(t.out.reduce((a, o) => a += o.value, 0)),
|
||||||
|
}))
|
||||||
|
|
||||||
|
})).catch(error => reject(error))
|
||||||
});
|
});
|
||||||
|
|
||||||
})('object' === typeof module ? module.exports : window.btcOperator = {});
|
})('object' === typeof module ? module.exports : window.btcOperator = {});
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
(function (EXPORTS) { //floExchangeAPI v1.1.3
|
(function (EXPORTS) { //floExchangeAPI v1.2.0
|
||||||
const exchangeAPI = EXPORTS;
|
const exchangeAPI = EXPORTS;
|
||||||
|
|
||||||
const DEFAULT = {
|
const DEFAULT = {
|
||||||
@ -1718,18 +1718,20 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _l = key => DEFAULT.marketApp + '-' + key;
|
||||||
|
|
||||||
exchangeAPI.init = function refreshDataFromBlockchain() {
|
exchangeAPI.init = function refreshDataFromBlockchain() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let nodes, trusted = new Set(), assets, tags, lastTx;
|
let nodes, trusted = new Set(), assets, tags, lastTx;
|
||||||
try {
|
try {
|
||||||
nodes = JSON.parse(localStorage.getItem('exchange-nodes'));
|
nodes = JSON.parse(localStorage.getItem(_l('nodes')));
|
||||||
trusted = new Set((localStorage.getItem('exchange-trusted') || "").split(','));
|
trusted = new Set((localStorage.getItem(_l('trusted')) || "").split(','));
|
||||||
assets = new Set((localStorage.getItem('exchange-assets') || "").split(','));
|
assets = new Set((localStorage.getItem(_l('assets')) || "").split(','));
|
||||||
tags = new Set((localStorage.getItem('exchange-tags') || "").split(','));
|
tags = new Set((localStorage.getItem(_l('tags')) || "").split(','));
|
||||||
if (typeof nodes !== 'object' || nodes === null)
|
if (typeof nodes !== 'object' || nodes === null)
|
||||||
throw Error('nodes must be an object')
|
throw Error('nodes must be an object')
|
||||||
else
|
else
|
||||||
lastTx = parseInt(localStorage.getItem('exchange-lastTx')) || 0;
|
lastTx = parseInt(localStorage.getItem(_l('lastTx'))) || 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
nodes = {};
|
nodes = {};
|
||||||
trusted = new Set();
|
trusted = new Set();
|
||||||
@ -1780,11 +1782,11 @@
|
|||||||
tags.add(t);
|
tags.add(t);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
localStorage.setItem('exchange-lastTx', result.totalTxs);
|
localStorage.setItem(_l('lastTx'), result.totalTxs);
|
||||||
localStorage.setItem('exchange-nodes', JSON.stringify(nodes));
|
localStorage.setItem(_l('nodes'), JSON.stringify(nodes));
|
||||||
localStorage.setItem('exchange-trusted', Array.from(trusted).join(","));
|
localStorage.setItem(_l('trusted'), Array.from(trusted).join(","));
|
||||||
localStorage.setItem('exchange-assets', Array.from(assets).join(","));
|
localStorage.setItem(_l('assets'), Array.from(assets).join(","));
|
||||||
localStorage.setItem('exchange-tags', Array.from(tags).join(","));
|
localStorage.setItem(_l('tags'), Array.from(tags).join(","));
|
||||||
nodeURL = nodes;
|
nodeURL = nodes;
|
||||||
nodeKBucket = new K_Bucket(DEFAULT.marketID, Object.keys(nodeURL));
|
nodeKBucket = new K_Bucket(DEFAULT.marketID, Object.keys(nodeURL));
|
||||||
nodeList = nodeKBucket.order;
|
nodeList = nodeKBucket.order;
|
||||||
@ -1795,25 +1797,117 @@
|
|||||||
|
|
||||||
const config = exchangeAPI.config = {
|
const config = exchangeAPI.config = {
|
||||||
get trustedList() {
|
get trustedList() {
|
||||||
return new Set((localStorage.getItem('exchange-trusted') || "").split(','));
|
return new Set((localStorage.getItem(_l('trusted')) || "").split(','));
|
||||||
},
|
},
|
||||||
get assetList() {
|
get assetList() {
|
||||||
return new Set((localStorage.getItem('exchange-assets') || "").split(','));
|
return new Set((localStorage.getItem(_l('assets')) || "").split(','));
|
||||||
},
|
},
|
||||||
get tagList() {
|
get tagList() {
|
||||||
return new Set((localStorage.getItem('exchange-tags') || "").split(','));
|
return new Set((localStorage.getItem(_l('tags')) || "").split(','));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exchangeAPI.clearAllLocalData = function () {
|
exchangeAPI.clearAllLocalData = function () {
|
||||||
localStorage.removeItem('exchange-nodes');
|
localStorage.removeItem(_l('nodes'));
|
||||||
localStorage.removeItem('exchange-trusted');
|
localStorage.removeItem(_l('trusted'));
|
||||||
localStorage.removeItem('exchange-assets');
|
localStorage.removeItem(_l('assets'));
|
||||||
localStorage.removeItem('exchange-tags');
|
localStorage.removeItem(_l('tags'));
|
||||||
localStorage.removeItem('exchange-lastTx');
|
localStorage.removeItem(_l('lastTx'));
|
||||||
localStorage.removeItem('exchange-proxy_secret');
|
localStorage.removeItem(_l('proxy_secret'));
|
||||||
localStorage.removeItem('exchange-user_ID');
|
localStorage.removeItem(_l('user_ID'));
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//container for user ID and proxy private-key
|
||||||
|
const proxy = exchangeAPI.proxy = {
|
||||||
|
user: null,
|
||||||
|
private: null,
|
||||||
|
public: null,
|
||||||
|
async lock() {
|
||||||
|
if (!this.private)
|
||||||
|
return notify("No proxy key found!", 'error');
|
||||||
|
getPromptInput("Add password", 'This password applies to this browser only!', {
|
||||||
|
isPassword: true,
|
||||||
|
confirmText: "Add password"
|
||||||
|
}).then(pwd => {
|
||||||
|
if (!pwd)
|
||||||
|
notify("Password cannot be empty", 'error');
|
||||||
|
else if (pwd.length < 4)
|
||||||
|
notify("Password minimum length is 4", 'error');
|
||||||
|
else {
|
||||||
|
let tmp = Crypto.AES.encrypt(this.private, pwd);
|
||||||
|
localStorage.setItem(_l('proxy_secret'), "?" + tmp);
|
||||||
|
notify("Successfully locked with Password", 'success');
|
||||||
|
}
|
||||||
|
}).catch(_ => null);
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
localStorage.removeItem(_l('proxy_secret'));
|
||||||
|
localStorage.removeItem(_l('user_ID'));
|
||||||
|
this.user = null;
|
||||||
|
this.private = null;
|
||||||
|
this.public = null;
|
||||||
|
},
|
||||||
|
get sinkID() {
|
||||||
|
return getRef("sink_id").value;
|
||||||
|
},
|
||||||
|
set userID(id) {
|
||||||
|
localStorage.setItem(_l('user_ID'), id);
|
||||||
|
this.user = id;
|
||||||
|
},
|
||||||
|
get userID() {
|
||||||
|
if (this.user)
|
||||||
|
return this.user;
|
||||||
|
else {
|
||||||
|
let id = localStorage.getItem(_l('user_ID'));
|
||||||
|
return id ? this.user = id : undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set secret(key) {
|
||||||
|
localStorage.setItem(_l('proxy_secret'), key);
|
||||||
|
this.private = key;
|
||||||
|
this.public = floCrypto.getPubKeyHex(key);
|
||||||
|
},
|
||||||
|
get secret() {
|
||||||
|
const self = this;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (self.private)
|
||||||
|
return resolve(self.private);
|
||||||
|
|
||||||
|
const Reject = reason => {
|
||||||
|
notify(reason, 'error');
|
||||||
|
reject(reason);
|
||||||
|
}
|
||||||
|
const setValues = priv => {
|
||||||
|
try {
|
||||||
|
self.private = priv;
|
||||||
|
self.public = floCrypto.getPubKeyHex(priv);
|
||||||
|
resolve(self.private);
|
||||||
|
} catch (error) {
|
||||||
|
Reject("Unable to fetch Proxy secret");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let tmp = localStorage.getItem(_l('proxy_secret'));
|
||||||
|
if (typeof tmp !== "string")
|
||||||
|
Reject("Unable to fetch Proxy secret");
|
||||||
|
else if (tmp.startsWith("?")) {
|
||||||
|
getPromptInput("Enter password", '', {
|
||||||
|
isPassword: true
|
||||||
|
}).then(pwd => {
|
||||||
|
if (!pwd)
|
||||||
|
return Reject("Password Required for making transactions");
|
||||||
|
try {
|
||||||
|
tmp = Crypto.AES.decrypt(tmp.substring(1), pwd);
|
||||||
|
setValues(tmp);
|
||||||
|
} catch (error) {
|
||||||
|
Reject("Incorrect Password! Password Required for making transactions");
|
||||||
|
|
||||||
|
}
|
||||||
|
}).catch(_ => Reject("Password Required for making transactions"));
|
||||||
|
} else
|
||||||
|
setValues(tmp);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
})('object' === typeof module ? module.exports : window.floExchangeAPI = {});
|
})('object' === typeof module ? module.exports : window.floExchangeAPI = {});
|
||||||
@ -19,7 +19,7 @@
|
|||||||
"setup": "node setup/post-install.js",
|
"setup": "node setup/post-install.js",
|
||||||
"configure": "node setup/configure-settings.js",
|
"configure": "node setup/configure-settings.js",
|
||||||
"reset-password": "node setup/reset-password.js",
|
"reset-password": "node setup/reset-password.js",
|
||||||
"create-schema": "node setup/create-schema.js",
|
"checksum-db": "node debug/checksum-db.js",
|
||||||
"start": "node start.js"
|
"start": "node start.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@ -51,13 +51,13 @@ function configureSQL() {
|
|||||||
flaggedYesOrNo('Do you want to re-configure mySQL connection').then(value => {
|
flaggedYesOrNo('Do you want to re-configure mySQL connection').then(value => {
|
||||||
if (value) {
|
if (value) {
|
||||||
console.log('Enter mySQL connection values: ')
|
console.log('Enter mySQL connection values: ')
|
||||||
getInput.Text('Host', config['sql_host']).then(host => {
|
getInput.Text('MySQL Host', config['sql_host']).then(host => {
|
||||||
config['sql_host'] = host;
|
config['sql_host'] = host;
|
||||||
getInput.Text('Database name', config['sql_db']).then(dbname => {
|
getInput.Text('Database name', config['sql_db']).then(dbname => {
|
||||||
config['sql_db'] = dbname;
|
config['sql_db'] = dbname;
|
||||||
getInput.Text('MySQL username', config['sql_user']).then(sql_user => {
|
getInput.Text('MySQL username', config['sql_user']).then(sql_user => {
|
||||||
config['sql_user'] = sql_user;
|
config['sql_user'] = sql_user;
|
||||||
getInput.Text('Mysql password', config['sql_pwd']).then(sql_pwd => {
|
getInput.Text('MySQL password', config['sql_pwd']).then(sql_pwd => {
|
||||||
config['sql_pwd'] = sql_pwd;
|
config['sql_pwd'] = sql_pwd;
|
||||||
resolve(true);
|
resolve(true);
|
||||||
})
|
})
|
||||||
@ -98,6 +98,7 @@ function configure() {
|
|||||||
return reject(false);
|
return reject(false);
|
||||||
}
|
}
|
||||||
console.log('Configuration successful!');
|
console.log('Configuration successful!');
|
||||||
|
/*
|
||||||
if (sql_result) {
|
if (sql_result) {
|
||||||
getInput.YesOrNo('Do you want to create schema in the database').then(value => {
|
getInput.YesOrNo('Do you want to create schema in the database').then(value => {
|
||||||
if (value) {
|
if (value) {
|
||||||
@ -113,7 +114,8 @@ function configure() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else
|
} else
|
||||||
resolve(true);
|
*/
|
||||||
|
resolve(true);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -5,9 +5,8 @@ Exchange market
|
|||||||
npm install - Install the app and node modules.
|
npm install - Install the app and node modules.
|
||||||
npm run help - List all commands.
|
npm run help - List all commands.
|
||||||
npm run setup - Finish the setup (configure and reset password).
|
npm run setup - Finish the setup (configure and reset password).
|
||||||
npm run configure - Configure the app.
|
npm run configure - Configure the node.
|
||||||
npm run reset-password - Reset the password (for private-key).
|
npm run reset-password - Reset the password (for private-key).
|
||||||
npm run create-schema - Create schema in MySQL database.
|
|
||||||
|
|
||||||
npm start - Start the application (main).
|
npm start - Start the application (main).
|
||||||
|
|
||||||
|
|||||||
@ -177,7 +177,7 @@ function confirmVaultWithdraw() {
|
|||||||
}).catch(error => console.error(error));
|
}).catch(error => console.error(error));
|
||||||
else if (r.asset == "BTC")
|
else if (r.asset == "BTC")
|
||||||
btcOperator.getTx(r.txid).then(tx => {
|
btcOperator.getTx(r.txid).then(tx => {
|
||||||
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
if (!tx.block || !tx.confirmations) //Still not confirmed
|
||||||
return;
|
return;
|
||||||
DB.query("UPDATE VaultTransactions SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
DB.query("UPDATE VaultTransactions SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
||||||
.then(result => console.info("BTC withdrawed:", r.floID, r.amount))
|
.then(result => console.info("BTC withdrawed:", r.floID, r.amount))
|
||||||
@ -203,7 +203,7 @@ verifyTx.BTC = function (sender, txid, group) {
|
|||||||
return reject([true, "Transaction not sent by the sender"]);
|
return reject([true, "Transaction not sent by the sender"]);
|
||||||
if (vin_sender.length !== tx.inputs.length)
|
if (vin_sender.length !== tx.inputs.length)
|
||||||
return reject([true, "Transaction input containes other floIDs"]);
|
return reject([true, "Transaction input containes other floIDs"]);
|
||||||
if (!tx.blockhash)
|
if (!tx.block)
|
||||||
return reject([false, "Transaction not included in any block yet"]);
|
return reject([false, "Transaction not included in any block yet"]);
|
||||||
if (!tx.confirmations)
|
if (!tx.confirmations)
|
||||||
return reject([false, "Transaction not confirmed yet"]);
|
return reject([false, "Transaction not confirmed yet"]);
|
||||||
@ -278,7 +278,7 @@ function confirmConvert() {
|
|||||||
results.forEach(r => {
|
results.forEach(r => {
|
||||||
if (r.mode == pCode.CONVERT_MODE_GET)
|
if (r.mode == pCode.CONVERT_MODE_GET)
|
||||||
btcOperator.getTx(r.out_txid).then(tx => {
|
btcOperator.getTx(r.out_txid).then(tx => {
|
||||||
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
if (!tx.block || !tx.confirmations) //Still not confirmed
|
||||||
return;
|
return;
|
||||||
DB.query("UPDATE DirectConvert SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
DB.query("UPDATE DirectConvert SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
||||||
.then(result => console.info(`${r.floID} converted ${r.amount} to ${r.quantity} BTC`))
|
.then(result => console.info(`${r.floID} converted ${r.amount} to ${r.quantity} BTC`))
|
||||||
@ -342,7 +342,7 @@ function confirmConvertFundWithdraw() {
|
|||||||
results.forEach(r => {
|
results.forEach(r => {
|
||||||
if (r.mode == pCode.CONVERT_MODE_GET) { //withdraw coin
|
if (r.mode == pCode.CONVERT_MODE_GET) { //withdraw coin
|
||||||
btcOperator.getTx(r.txid).then(tx => {
|
btcOperator.getTx(r.txid).then(tx => {
|
||||||
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
if (!tx.block || !tx.confirmations) //Still not confirmed
|
||||||
return;
|
return;
|
||||||
DB.query("UPDATE ConvertFund SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
DB.query("UPDATE ConvertFund SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
||||||
.then(result => console.info(`Withdraw-fund ${r.quantity} ${r.coin} successful`))
|
.then(result => console.info(`Withdraw-fund ${r.quantity} ${r.coin} successful`))
|
||||||
@ -399,7 +399,7 @@ function confirmConvertRefund() {
|
|||||||
if (r.ASSET_TYPE_COIN) {
|
if (r.ASSET_TYPE_COIN) {
|
||||||
if (r.asset == "BTC") //Convert is only for BTC right now
|
if (r.asset == "BTC") //Convert is only for BTC right now
|
||||||
btcOperator.getTx(r.out_txid).then(tx => {
|
btcOperator.getTx(r.out_txid).then(tx => {
|
||||||
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
if (!tx.block || !tx.confirmations) //Still not confirmed
|
||||||
return;
|
return;
|
||||||
DB.query("UPDATE RefundConvert SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
DB.query("UPDATE RefundConvert SET r_status=? WHERE id=?", [pCode.STATUS_SUCCESS, r.id])
|
||||||
.then(result => console.info(`Refunded ${r.amount} ${r.asset} to ${r.floID}`))
|
.then(result => console.info(`Refunded ${r.amount} ${r.asset} to ${r.floID}`))
|
||||||
@ -427,7 +427,7 @@ function confirmBondClosing() {
|
|||||||
DB.query("SELECT * FROM CloseBondTransact WHERE r_status=?", [pCode.STATUS_CONFIRMATION]).then(results => {
|
DB.query("SELECT * FROM CloseBondTransact WHERE r_status=?", [pCode.STATUS_CONFIRMATION]).then(results => {
|
||||||
results.forEach(r => {
|
results.forEach(r => {
|
||||||
btcOperator.getTx(r.txid).then(tx => {
|
btcOperator.getTx(r.txid).then(tx => {
|
||||||
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
if (!tx.block || !tx.confirmations) //Still not confirmed
|
||||||
return;
|
return;
|
||||||
let closeBondString = bond_util.stringify.end(r.bond_id, r.end_date, r.btc_net, r.usd_net, r.amount, r.ref_sign, r.txid);
|
let closeBondString = bond_util.stringify.end(r.bond_id, r.end_date, r.btc_net, r.usd_net, r.amount, r.ref_sign, r.txid);
|
||||||
floBlockchainAPI.writeData(keys.node_id, closeBondString, keys.node_priv, bond_util.config.adminID).then(txid => {
|
floBlockchainAPI.writeData(keys.node_id, closeBondString, keys.node_priv, bond_util.config.adminID).then(txid => {
|
||||||
@ -450,7 +450,7 @@ function confirmFundClosing() {
|
|||||||
DB.query("SELECT * FROM CloseFundTransact WHERE r_status=?", [pCode.STATUS_CONFIRMATION]).then(results => {
|
DB.query("SELECT * FROM CloseFundTransact WHERE r_status=?", [pCode.STATUS_CONFIRMATION]).then(results => {
|
||||||
results.forEach(r => {
|
results.forEach(r => {
|
||||||
btcOperator.getTx(r.txid).then(tx => {
|
btcOperator.getTx(r.txid).then(tx => {
|
||||||
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
|
if (!tx.block || !tx.confirmations) //Still not confirmed
|
||||||
return;
|
return;
|
||||||
let closeFundString = fund_util.stringify.end(r.fund_id, r.floID, r.end_date, r.btc_net, r.usd_net, r.amount, r.ref_sign, r.txid);
|
let closeFundString = fund_util.stringify.end(r.fund_id, r.floID, r.end_date, r.btc_net, r.usd_net, r.amount, r.ref_sign, r.txid);
|
||||||
floBlockchainAPI.writeData(keys.node_id, closeFundString, keys.node_priv, fund_util.config.adminID).then(txid => {
|
floBlockchainAPI.writeData(keys.node_id, closeFundString, keys.node_priv, fund_util.config.adminID).then(txid => {
|
||||||
|
|||||||
@ -245,9 +245,21 @@ function updateMaster(floID) {
|
|||||||
connectToMaster();
|
connectToMaster();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reconstructAllActiveShares() {
|
||||||
|
if (_mode !== MASTER_MODE)
|
||||||
|
return console.debug("Not serving as master");
|
||||||
|
console.debug("Reconstructing shares for all active IDs")
|
||||||
|
let group_list = keys.sink_groups.list;
|
||||||
|
group_list.forEach(g => {
|
||||||
|
//active ids also ignore ids that are in queue for reconstructing shares
|
||||||
|
let active_ids = keys.sink_chest.active_list(g);
|
||||||
|
active_ids.forEach(id => reconstructShares(g, id));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function reconstructShares(group, sinkID) {
|
function reconstructShares(group, sinkID) {
|
||||||
if (_mode !== MASTER_MODE)
|
if (_mode !== MASTER_MODE)
|
||||||
return console.warn("Not serving as master");
|
return console.warn(`Not serving as master, but reconstruct-shares is called for ${sinkID}(${group})`);
|
||||||
keys.sink_chest.set_id(group, sinkID, null);
|
keys.sink_chest.set_id(group, sinkID, null);
|
||||||
collectAndCall(group, sinkID, sinkKey => sendSharesToNodes(sinkID, group, generateShares(sinkKey)));
|
collectAndCall(group, sinkID, sinkKey => sendSharesToNodes(sinkID, group, generateShares(sinkKey)));
|
||||||
}
|
}
|
||||||
@ -460,6 +472,7 @@ function initProcess(app) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
init: initProcess,
|
init: initProcess,
|
||||||
collectAndCall,
|
collectAndCall,
|
||||||
|
reconstructAllActiveShares,
|
||||||
sink: {
|
sink: {
|
||||||
generate: generateSink,
|
generate: generateSink,
|
||||||
reshare: reshareSink,
|
reshare: reshareSink,
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const keys = require("../keys");
|
const keys = require("../keys");
|
||||||
const DB = require("../database");
|
const DB = require("../database");
|
||||||
|
const { getTableHashes } = require("./sync");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
BACKUP_INTERVAL,
|
BACKUP_INTERVAL,
|
||||||
@ -315,7 +316,7 @@ function deleteTableData(data) {
|
|||||||
data.forEach(r => r.t_name in delete_needed ? delete_needed[r.t_name].push(r.id) : delete_needed[r.t_name] = [r.id]);
|
data.forEach(r => r.t_name in delete_needed ? delete_needed[r.t_name].push(r.id) : delete_needed[r.t_name] = [r.id]);
|
||||||
let queries = [];
|
let queries = [];
|
||||||
for (let table in delete_needed)
|
for (let table in delete_needed)
|
||||||
queries.push(`DELETE FROM ${table} WHERE id IN (${delete_needed[table]})`);
|
queries.push("DELETE FROM ?? WHERE id IN (?)", [table, delete_needed[table]]);
|
||||||
DB.transaction(queries).then(_ => resolve(true)).catch(error => reject(error));
|
DB.transaction(queries).then(_ => resolve(true)).catch(error => reject(error));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -326,9 +327,10 @@ function updateTableData(table, data) {
|
|||||||
return resolve(null);
|
return resolve(null);
|
||||||
let cols = Object.keys(data[0]);
|
let cols = Object.keys(data[0]);
|
||||||
let values = data.map(r => cols.map(c => validateValue(r[c])));
|
let values = data.map(r => cols.map(c => validateValue(r[c])));
|
||||||
let statement = `INSERT INTO ${table} (${cols}) VALUES ?` +
|
let statement = "INSERT INTO ?? (??) VALUES ? ON DUPLICATE KEY UPDATE " + Array(cols.length).fill("??=VALUES(??)").join();
|
||||||
" ON DUPLICATE KEY UPDATE " + cols.map(c => `${c}=VALUES(${c})`).join();
|
let query_values = [table, cols, values];
|
||||||
DB.query(statement, [values]).then(_ => resolve(true)).catch(error => reject(error));
|
cols.forEach(c => query_values.push(c, c));
|
||||||
|
DB.query(statement, query_values).then(_ => resolve(true)).catch(error => reject(error));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,7 +338,7 @@ const validateValue = val => (typeof val === "string" && /\.\d{3}Z$/.test(val))
|
|||||||
|
|
||||||
function verifyChecksum(checksum_ref) {
|
function verifyChecksum(checksum_ref) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
DB.query("CHECKSUM TABLE " + Object.keys(checksum_ref).join()).then(result => {
|
DB.query("CHECKSUM TABLE ??", [Object.keys(checksum_ref)]).then(result => {
|
||||||
let checksum = Object.fromEntries(result.map(r => [r.Table.split(".").pop(), r.Checksum]));
|
let checksum = Object.fromEntries(result.map(r => [r.Table.split(".").pop(), r.Checksum]));
|
||||||
let mismatch = [];
|
let mismatch = [];
|
||||||
for (let table in checksum)
|
for (let table in checksum)
|
||||||
@ -374,17 +376,9 @@ function requestHash(tables) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function verifyHash(hashes) {
|
function verifyHash(hashes) {
|
||||||
const getHash = table => new Promise((res, rej) => {
|
|
||||||
DB.query("SHOW COLUMNS FROM " + table).then(result => {
|
|
||||||
let columns = result.map(r => r["Field"]).sort();
|
|
||||||
DB.query(`SELECT CEIL(id/${HASH_N_ROW}) as group_id, MD5(GROUP_CONCAT(${columns.map(c => `IFNULL(${c}, "NULL")`).join()})) as hash FROM ${table} GROUP BY group_id ORDER BY group_id`)
|
|
||||||
.then(result => res(Object.fromEntries(result.map(r => [r.group_id, r.hash]))))
|
|
||||||
.catch(error => rej(error))
|
|
||||||
}).catch(error => rej(error))
|
|
||||||
});
|
|
||||||
const convertIntArray = obj => Object.keys(obj).map(i => parseInt(i));
|
const convertIntArray = obj => Object.keys(obj).map(i => parseInt(i));
|
||||||
const checkHash = (table, hash_ref) => new Promise((res, rej) => {
|
const checkHash = (table, hash_ref) => new Promise((res, rej) => {
|
||||||
getHash(table).then(hash_cur => {
|
getTableHashes(table).then(hash_cur => {
|
||||||
for (let i in hash_ref)
|
for (let i in hash_ref)
|
||||||
if (hash_ref[i] === hash_cur[i]) {
|
if (hash_ref[i] === hash_cur[i]) {
|
||||||
delete hash_ref[i];
|
delete hash_ref[i];
|
||||||
@ -403,8 +397,8 @@ function verifyHash(hashes) {
|
|||||||
//Data to be deleted (incorrect data will be added by resync)
|
//Data to be deleted (incorrect data will be added by resync)
|
||||||
let id_end = result[t].value[1].map(i => i * HASH_N_ROW); //eg if i=2 AND H_R_C = 5 then id_end = 2 * 5 = 10 (ie, range 6-10)
|
let id_end = result[t].value[1].map(i => i * HASH_N_ROW); //eg if i=2 AND H_R_C = 5 then id_end = 2 * 5 = 10 (ie, range 6-10)
|
||||||
Promise.allSettled(id_end.map(i =>
|
Promise.allSettled(id_end.map(i =>
|
||||||
DB.query(`DELETE FROM ${tables[t]} WHERE id BETWEEN ${i - HASH_N_ROW + 1} AND ${i}`))) //eg, i - HASH_N_ROW + 1 = 10 - 5 + 1 = 6
|
DB.query("DELETE FROM ?? WHERE id BETWEEN ? AND ?", [tables[t], i - HASH_N_ROW + 1, i]) //eg, i - HASH_N_ROW + 1 = 10 - 5 + 1 = 6
|
||||||
.then(_ => null);
|
)).then(_ => null);
|
||||||
} else
|
} else
|
||||||
console.error(result[t].reason);
|
console.error(result[t].reason);
|
||||||
//console.debug("Hash-mismatch", mismatch);
|
//console.debug("Hash-mismatch", mismatch);
|
||||||
|
|||||||
@ -54,7 +54,7 @@ function backupSync_delete(last_time, ws) {
|
|||||||
|
|
||||||
function backupSync_data(last_time, ws) {
|
function backupSync_data(last_time, ws) {
|
||||||
const sendTable = (table, id_list) => new Promise((res, rej) => {
|
const sendTable = (table, id_list) => new Promise((res, rej) => {
|
||||||
DB.query(`SELECT * FROM ${table} WHERE id IN (${id_list})`)
|
DB.query("SELECT * FROM ?? WHERE id IN (?)", [table, id_list])
|
||||||
.then(data => {
|
.then(data => {
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
table,
|
table,
|
||||||
@ -99,7 +99,7 @@ function backupSync_checksum(ws) {
|
|||||||
let tableList = result.map(r => r['t_name']);
|
let tableList = result.map(r => r['t_name']);
|
||||||
if (!tableList.length)
|
if (!tableList.length)
|
||||||
return resolve("checksum");
|
return resolve("checksum");
|
||||||
DB.query("CHECKSUM TABLE " + tableList.join()).then(result => {
|
DB.query("CHECKSUM TABLE ??", [tableList]).then(result => {
|
||||||
let checksum = Object.fromEntries(result.map(r => [r.Table.split(".").pop(), r.Checksum]));
|
let checksum = Object.fromEntries(result.map(r => [r.Table.split(".").pop(), r.Checksum]));
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
command: "SYNC_CHECKSUM",
|
command: "SYNC_CHECKSUM",
|
||||||
@ -119,15 +119,7 @@ function backupSync_checksum(ws) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sendTableHash(tables, ws) {
|
function sendTableHash(tables, ws) {
|
||||||
const getHash = table => new Promise((res, rej) => {
|
Promise.allSettled(tables.map(t => getTableHashes(t))).then(result => {
|
||||||
DB.query("SHOW COLUMNS FROM " + table).then(result => {
|
|
||||||
let columns = result.map(r => r["Field"]).sort();
|
|
||||||
DB.query(`SELECT CEIL(id/${HASH_N_ROW}) as group_id, MD5(GROUP_CONCAT(${columns.map(c => `IFNULL(${c}, "NULL")`).join()})) as hash FROM ${table} GROUP BY group_id ORDER BY group_id`)
|
|
||||||
.then(result => res(Object.fromEntries(result.map(r => [r.group_id, r.hash]))))
|
|
||||||
.catch(error => rej(error))
|
|
||||||
}).catch(error => rej(error))
|
|
||||||
});
|
|
||||||
Promise.allSettled(tables.map(t => getHash(t))).then(result => {
|
|
||||||
let hashes = {};
|
let hashes = {};
|
||||||
for (let i in tables)
|
for (let i in tables)
|
||||||
if (result[i].status === "fulfilled")
|
if (result[i].status === "fulfilled")
|
||||||
@ -141,6 +133,28 @@ function sendTableHash(tables, ws) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTableHashes(table) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
DB.query("SHOW COLUMNS FROM ??", [table]).then(result => {
|
||||||
|
//columns
|
||||||
|
let columns = result.map(r => r["Field"]).sort();
|
||||||
|
//select statement
|
||||||
|
let statement = "SELECT CEIL(id/?) as group_id";
|
||||||
|
let query_values = [HASH_N_ROW];
|
||||||
|
//aggregate column values
|
||||||
|
let col_aggregate = columns.map(c => "IFNULL(CRC32(??), 0)").join('+');
|
||||||
|
columns.forEach(c => query_values.push(c));
|
||||||
|
//aggregate rows via group by
|
||||||
|
statement += " SUM(CRC32(MD5(" + col_aggregate + "))) as hash FROM ?? GROUP BY group_id ORDER BY group_id";
|
||||||
|
query_values.push(table);
|
||||||
|
//query
|
||||||
|
DB.query(statement, query_values)
|
||||||
|
.then(result => resolve(Object.fromEntries(result.map(r => [r.group_id, r.hash]))))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function sendTableData(tables, ws) {
|
function sendTableData(tables, ws) {
|
||||||
let promises = [
|
let promises = [
|
||||||
tableSync_data(tables, ws),
|
tableSync_data(tables, ws),
|
||||||
@ -196,7 +210,7 @@ function tableSync_data(tables, ws) {
|
|||||||
const sendTable = (table, group_id) => new Promise((res, rej) => {
|
const sendTable = (table, group_id) => new Promise((res, rej) => {
|
||||||
let id_end = group_id * HASH_N_ROW,
|
let id_end = group_id * HASH_N_ROW,
|
||||||
id_start = id_end - HASH_N_ROW + 1;
|
id_start = id_end - HASH_N_ROW + 1;
|
||||||
DB.query(`SELECT * FROM ${table} WHERE id BETWEEN ? AND ?`, [id_start, id_end]).then(data => {
|
DB.query("SELECT * FROM ?? WHERE id BETWEEN ? AND ?", [table, id_start, id_end]).then(data => {
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
table,
|
table,
|
||||||
command: "SYNC_UPDATE",
|
command: "SYNC_UPDATE",
|
||||||
@ -245,7 +259,7 @@ function tableSync_data(tables, ws) {
|
|||||||
|
|
||||||
function tableSync_checksum(tables, ws) {
|
function tableSync_checksum(tables, ws) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
DB.query("CHECKSUM TABLE " + tables.join()).then(result => {
|
DB.query("CHECKSUM TABLE ??", [tables]).then(result => {
|
||||||
let checksum = Object.fromEntries(result.map(r => [r.Table.split(".").pop(), r.Checksum]));
|
let checksum = Object.fromEntries(result.map(r => [r.Table.split(".").pop(), r.Checksum]));
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
command: "SYNC_CHECKSUM",
|
command: "SYNC_CHECKSUM",
|
||||||
@ -261,6 +275,7 @@ function tableSync_checksum(tables, ws) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
getTableHashes,
|
||||||
sendBackupData,
|
sendBackupData,
|
||||||
sendTableHash,
|
sendTableHash,
|
||||||
sendTableData
|
sendTableData
|
||||||
|
|||||||
@ -345,6 +345,9 @@ const sink_groups = {
|
|||||||
get CONVERT() { return "convert" },
|
get CONVERT() { return "convert" },
|
||||||
get BLOCKCHAIN_BONDS() { return "blockchain_bonds" },
|
get BLOCKCHAIN_BONDS() { return "blockchain_bonds" },
|
||||||
get BOBS_FUND() { return "bobs_fund" },
|
get BOBS_FUND() { return "bobs_fund" },
|
||||||
|
get list() { //total list
|
||||||
|
return [this.EXCHANGE, this.CONVERT, this.BLOCKCHAIN_BONDS, this.BOBS_FUND]
|
||||||
|
},
|
||||||
get initial_list() { //list to generate when starting exchange
|
get initial_list() { //list to generate when starting exchange
|
||||||
return [this.EXCHANGE, this.CONVERT]
|
return [this.EXCHANGE, this.CONVERT]
|
||||||
},
|
},
|
||||||
|
|||||||
12
src/main.js
12
src/main.js
@ -27,8 +27,10 @@ var app;
|
|||||||
|
|
||||||
function refreshData(startup = false) {
|
function refreshData(startup = false) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
refreshDataFromBlockchain().then(result => {
|
refreshDataFromBlockchain().then(changes => {
|
||||||
loadDataFromDB(result, startup).then(_ => {
|
loadDataFromDB(changes, startup).then(_ => {
|
||||||
|
if (!startup && changes.nodes)
|
||||||
|
backup.reconstructAllActiveShares();
|
||||||
app.refreshData(backup.nodeList);
|
app.refreshData(backup.nodeList);
|
||||||
resolve("Data refresh successful")
|
resolve("Data refresh successful")
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
@ -198,7 +200,11 @@ module.exports = function startServer() {
|
|||||||
app.start(config['port']).then(result => {
|
app.start(config['port']).then(result => {
|
||||||
console.log(result);
|
console.log(result);
|
||||||
backup.init(app);
|
backup.init(app);
|
||||||
setInterval(refreshData, BLOCKCHAIN_REFRESH_INTERVAL)
|
setInterval(() => {
|
||||||
|
refreshData()
|
||||||
|
.then(result => console.log(result))
|
||||||
|
.catch(error => console.error(error))
|
||||||
|
}, BLOCKCHAIN_REFRESH_INTERVAL);
|
||||||
}).catch(error => console.error(error))
|
}).catch(error => console.error(error))
|
||||||
}).catch(error => console.error(error))
|
}).catch(error => console.error(error))
|
||||||
}).catch(error => console.error(error))
|
}).catch(error => console.error(error))
|
||||||
|
|||||||
@ -202,14 +202,14 @@ function cancelOrder(type, id, floID) {
|
|||||||
tableName = "SellOrder";
|
tableName = "SellOrder";
|
||||||
else
|
else
|
||||||
return reject(INVALID(eCode.INVALID_TYPE, "Invalid Order type! Order type must be buy (or) sell"));
|
return reject(INVALID(eCode.INVALID_TYPE, "Invalid Order type! Order type must be buy (or) sell"));
|
||||||
DB.query(`SELECT floID, asset FROM ${tableName} WHERE id=?`, [id]).then(result => {
|
DB.query("SELECT floID, asset FROM ?? WHERE id=?", [tableName, id]).then(result => {
|
||||||
if (result.length < 1)
|
if (result.length < 1)
|
||||||
return reject(INVALID(eCode.NOT_FOUND, "Order not found!"));
|
return reject(INVALID(eCode.NOT_FOUND, "Order not found!"));
|
||||||
else if (result[0].floID !== floID)
|
else if (result[0].floID !== floID)
|
||||||
return reject(INVALID(eCode.NOT_OWNER, "Order doesnt belong to the current user"));
|
return reject(INVALID(eCode.NOT_OWNER, "Order doesnt belong to the current user"));
|
||||||
let asset = result[0].asset;
|
let asset = result[0].asset;
|
||||||
//Delete the order
|
//Delete the order
|
||||||
DB.query(`DELETE FROM ${tableName} WHERE id=?`, [id]).then(result => {
|
DB.query("DELETE FROM ?? WHERE id=?", [tableName, id]).then(result => {
|
||||||
resolve(tableName + "#" + id + " cancelled successfully");
|
resolve(tableName + "#" + id + " cancelled successfully");
|
||||||
coupling.initiate(asset);
|
coupling.initiate(asset);
|
||||||
}).catch(error => reject(error));
|
}).catch(error => reject(error));
|
||||||
@ -219,11 +219,11 @@ function cancelOrder(type, id, floID) {
|
|||||||
|
|
||||||
function getAccountDetails(floID) {
|
function getAccountDetails(floID) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let select = [];
|
let promises = [
|
||||||
select.push(["token, quantity", "UserBalance"]);
|
DB.query("SELECT token, quantity FROM UserBalance WHERE floID=?", [floID]),
|
||||||
select.push(["id, asset, quantity, minPrice, time_placed", "SellOrder"]);
|
DB.query("SELECT id, asset, quantity, minPrice, time_placed FROM SellOrder WHERE floID=?", [floID]),
|
||||||
select.push(["id, asset, quantity, maxPrice, time_placed", "BuyOrder"]);
|
DB.query("SELECT id, asset, quantity, maxPrice, time_placed FROM BuyOrder WHERE floID=?", [floID])
|
||||||
let promises = select.map(a => DB.query(`SELECT ${a[0]} FROM ${a[1]} WHERE floID=? ${a[2] || ""}`, [floID]));
|
];
|
||||||
Promise.allSettled(promises).then(results => {
|
Promise.allSettled(promises).then(results => {
|
||||||
let response = {
|
let response = {
|
||||||
floID: floID,
|
floID: floID,
|
||||||
@ -272,7 +272,7 @@ function getTransactionDetails(txid) {
|
|||||||
type = 'trade';
|
type = 'trade';
|
||||||
} else
|
} else
|
||||||
return reject(INVALID(eCode.INVALID_TX_ID, "Invalid TransactionID"));
|
return reject(INVALID(eCode.INVALID_TX_ID, "Invalid TransactionID"));
|
||||||
DB.query(`SELECT * FROM ${tableName} WHERE txid=?`, [txid]).then(result => {
|
DB.query("SELECT * FROM ?? WHERE txid=?", [tableName, txid]).then(result => {
|
||||||
if (result.length) {
|
if (result.length) {
|
||||||
let details = result[0];
|
let details = result[0];
|
||||||
details.type = type;
|
details.type = type;
|
||||||
|
|||||||
34
src/price.js
34
src/price.js
@ -48,42 +48,46 @@ function getPastRate(asset, hrs = 24) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHistory(asset, duration) {
|
function getHistory(asset, duration = '') {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
duration = getHistory.validateDuration(duration);
|
let { statement, values } = getHistory.getRateStatement(asset, duration);
|
||||||
let statement = "SELECT " +
|
DB.query(statement, values)
|
||||||
(!duration || duration.endsWith("month") || duration.endsWith("year") ? "DATE(rec_time) AS time, AVG(rate) as rate" : "rec_time AS time, rate") +
|
|
||||||
" FROM PriceHistory WHERE asset=?" + (duration ? " AND rec_time >= NOW() - INTERVAL " + duration : "") +
|
|
||||||
(!duration || duration.endsWith("month") || duration.endsWith("year") ? " GROUP BY time" : "") +
|
|
||||||
" ORDER BY time";
|
|
||||||
DB.query(statement, asset)
|
|
||||||
.then(result => resolve(result))
|
.then(result => resolve(result))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getHistory.validateDuration = duration => {
|
getHistory.statement = {
|
||||||
|
'all-time': "SELECT DATE(rec_time) AS time, AVG(rate) as rate FROM PriceHistory WHERE asset=? GROUP BY time ORDER BY time",
|
||||||
|
'year': "SELECT DATE(rec_time) AS time, AVG(rate) as rate FROM PriceHistory WHERE asset=? AND rec_time >= NOW() - INTERVAL ? year GROUP BY time ORDER BY time",
|
||||||
|
'month': "SELECT DATE(rec_time) AS time, AVG(rate) as rate FROM PriceHistory WHERE asset=? AND rec_time >= NOW() - INTERVAL ? month GROUP BY time ORDER BY time",
|
||||||
|
'week': "SELECT rec_time AS time, rate FROM PriceHistory WHERE asset=? AND rec_time >= NOW() - INTERVAL ? week ORDER BY time",
|
||||||
|
'day': "SELECT rec_time AS time, rate FROM PriceHistory WHERE asset=? AND rec_time >= NOW() - INTERVAL ? day ORDER BY time"
|
||||||
|
}
|
||||||
|
|
||||||
|
getHistory.getRateStatement = (asset, duration) => {
|
||||||
let n = duration.match(/\d+/g),
|
let n = duration.match(/\d+/g),
|
||||||
d = duration.match(/\D+/g);
|
d = duration.match(/\D+/g);
|
||||||
n = n ? n[0] || 1 : 1;
|
n = n ? n[0] || 1 : 1;
|
||||||
d = d ? d[0].replace(/[-\s]/g, '') : "";
|
d = d ? d[0].replace(/[-\s]/g, '') : "";
|
||||||
|
|
||||||
switch (d.toLowerCase()) {
|
switch (d.toLowerCase()) {
|
||||||
case "day":
|
case "day":
|
||||||
case "days":
|
case "days":
|
||||||
return n + " day";
|
return { statement: getHistory.statement['day'], values: [asset, n] };
|
||||||
case "week":
|
case "week":
|
||||||
case "weeks":
|
case "weeks":
|
||||||
return n + " week";
|
return { statement: getHistory.statement['week'], values: [asset, n] };
|
||||||
case "month":
|
case "month":
|
||||||
case "months":
|
case "months":
|
||||||
return n + " month";
|
return { statement: getHistory.statement['month'], values: [asset, n] };
|
||||||
case "year":
|
case "year":
|
||||||
case "years":
|
case "years":
|
||||||
return n + " year";
|
return { statement: getHistory.statement['year'], values: [asset, n] };
|
||||||
case "alltime":
|
case "alltime":
|
||||||
return null;
|
return { statement: getHistory.statement['all-time'], values: [asset] };
|
||||||
default:
|
default:
|
||||||
return '1 day';
|
return { statement: getHistory.statement['day'], values: [asset, 1] };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -221,7 +221,9 @@ function refreshBlockchainData(nodeList = []) {
|
|||||||
let values = [fund_id, fund.start_date, fund.BTC_base, fund.USD_base, fund.fee, fund.duration];
|
let values = [fund_id, fund.start_date, fund.BTC_base, fund.USD_base, fund.fee, fund.duration];
|
||||||
if (fund.tapoutInterval)
|
if (fund.tapoutInterval)
|
||||||
values.push(fund.topoutWindow, fund.tapoutInterval.join(','));
|
values.push(fund.topoutWindow, fund.tapoutInterval.join(','));
|
||||||
txQueries.push([`INSERT INTO BobsFund(fund_id, begin_date, btc_base, usd_base, fee, duration ${fund.tapoutInterval ? ", tapout_window, tapout_interval" : ""}) VALUE (?) ON DUPLICATE KEY UPDATE fund_id=fund_id`, [values]])
|
else
|
||||||
|
values.push(null, null);
|
||||||
|
txQueries.push(["INSERT INTO BobsFund(fund_id, begin_date, btc_base, usd_base, fee, duration, tapout_window, tapout_interval) VALUE (?) ON DUPLICATE KEY UPDATE fund_id=fund_id", [values]])
|
||||||
} else
|
} else
|
||||||
fund_id = fund_id.pop().match(/[a-z0-9]{64}/).pop();
|
fund_id = fund_id.pop().match(/[a-z0-9]{64}/).pop();
|
||||||
let investments = Object.entries(fund.investments).map(a => [fund_id, a[0], a[1].amount]);
|
let investments = Object.entries(fund.investments).map(a => [fund_id, a[0], a[1].amount]);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user