Merge pull request #1 from ranchimall/dev

Dev
This commit is contained in:
sairaj mote 2022-12-05 01:13:57 +05:30 committed by GitHub
commit 506b7bd82a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 14585 additions and 11019 deletions

View File

@ -1,12 +1,12 @@
<svg width="1440" height="541" viewBox="0 0 1440 541" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<rect x="-89" y="280.638" width="479.275" height="120.308" rx="12" transform="rotate(-30 -89 280.638)" fill="#DA173A"/>
<rect x="-202" y="461.5" width="389" height="66" rx="12" transform="rotate(-30 -202 461.5)" fill="#BD0728"/>
<rect x="1543.22" y="283.351" width="424.814" height="106.637" rx="12" transform="rotate(150 1543.22 283.351)" fill="#FF6B6B"/>
<g clip-path="url(#clip0_66_94)">
<rect x="-89" y="280.638" width="479.275" height="120.308" rx="12" transform="rotate(-30 -89 280.638)" fill="#7795FF"/>
<rect x="-202" y="461.5" width="389" height="66" rx="12" transform="rotate(-30 -202 461.5)" fill="#360095"/>
<rect x="1543.22" y="283.351" width="424.814" height="106.637" rx="12" transform="rotate(150 1543.22 283.351)" fill="#FAB2D9"/>
<rect x="1483.26" y="214.703" width="233.236" height="58.5471" rx="12" transform="rotate(150 1483.26 214.703)" fill="white"/>
</g>
<defs>
<clipPath id="clip0">
<clipPath id="clip0_66_94">
<rect width="1440" height="541" fill="white"/>
</clipPath>
</defs>

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 733 B

View File

@ -2,65 +2,89 @@
padding: 0;
margin: 0;
box-sizing: border-box;
font-family: "Inter", sans-serif;
font-family: "Roboto", sans-serif;
}
:root {
font-size: clamp(1rem, 1.2vmax, 3rem);
font-size: clamp(16px, 1.2vmax, 32px);
}
html, body {
html,
body {
height: 100%;
}
body {
--accent-color: #FF2D46;
--light-shade: rgba(var(--text-color), 0.06);
--text-color: 17, 17, 17;
--text-color-light: 100, 100, 100;
--foreground-color: 255, 255, 255;
--background-color: #F6f6f6;
--error-color: red;
--green: #00843b;
--accent-color: #5101dc;
--text-color: 20, 20, 20;
--foreground-color: 252, 253, 255;
--background-color: 241, 243, 248;
--danger-color: rgb(255, 75, 75);
--green: #1cad59;
--yellow: rgb(220, 165, 0);
color: rgba(var(--text-color), 1);
background: var(--background-color);
background: rgba(var(--background-color), 1);
}
body[data-theme=dark] {
--accent-color: #d8152b;
--green: #13ff5a;
--text-color: 240, 240, 240;
--text-color-light: 170, 170, 170;
--foreground-color: 20, 20, 20;
--background-color: #0a0a0a;
--error-color: rgb(255, 106, 106);
--accent-color: #c8a8ff;
--text-color: 200, 200, 200;
--foreground-color: 27, 28, 29;
--background-color: 21, 22, 22;
--danger-color: rgb(255, 106, 106);
--green: #00e676;
--yellow: rgb(255, 213, 5);
}
body[data-theme=dark] ::-webkit-calendar-picker-indicator {
filter: invert(1);
}
.full-bleed {
grid-column: 1/4 !important;
.overpass {
font-family: "Overpass", sans-serif;
font-weight: 700;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: "Overpass", sans-serif;
font-weight: 700;
}
.h1 {
font-size: 2.5rem;
font-size: 6rem;
}
.h2 {
font-size: 2rem;
font-size: 4rem;
}
.h3 {
font-size: 1.4rem;
font-size: 3rem;
line-height: 1.1;
}
.h4 {
font-size: 1rem;
font-size: 2rem;
}
.h5 {
font-size: 0.8rem;
font-size: 1.5rem;
}
.h6 {
font-size: 1.2rem;
}
.body-1 {
font-size: 1rem;
}
.body-2 {
font-size: 0.85rem;
}
.uppercase {
@ -79,58 +103,140 @@ p {
}
img {
object-fit: cover;
-o-object-fit: cover;
object-fit: cover;
}
a {
color: inherit;
color: var(--accent-color);
text-decoration: none;
}
a:focus-visible {
box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1) inset;
a:-webkit-any-link:focus-visible {
outline: var(--accent-color) medium solid;
}
button {
a:-moz-any-link:focus-visible {
outline: var(--accent-color) medium solid;
}
a:any-link:focus-visible {
outline: var(--accent-color) medium solid;
}
button,
.button {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
position: relative;
display: inline-flex;
border: none;
background-color: transparent;
overflow: hidden;
align-items: center;
background: none;
cursor: pointer;
outline: none;
color: inherit;
-webkit-tap-highlight-color: transparent;
align-items: center;
font-size: 0.9rem;
font-weight: 500;
white-space: nowrap;
padding: 0.8rem;
border-radius: 0.3rem;
padding: 0.4rem 0.6rem;
-webkit-tap-highlight-color: transparent;
border: none;
justify-content: center;
}
button:focus-visible,
.button:focus-visible {
outline: var(--accent-color) solid medium;
}
button:not(:disabled),
.button:not(:disabled) {
cursor: pointer;
}
.button {
background-color: rgba(var(--text-color), 0.02);
border: solid thin rgba(var(--text-color), 0.06);
}
.button--primary {
background: var(--accent-color);
color: white;
color: rgba(var(--background-color), 1) !important;
}
.button--primary .icon {
fill: rgba(var(--foreground-color), 1);
fill: rgba(var(--background-color), 1);
}
.button--danger {
color: var(--danger-color);
}
.button--danger .icon {
fill: var(--danger-color);
}
.button--primary {
background-color: var(--accent-color);
}
.button--colored {
color: var(--accent-color);
}
.button--colored .icon {
fill: var(--accent-color);
}
.button--small {
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:focus-visible {
outline: rgba(var(--text-color), 1) 0.1rem solid;
button:disabled {
opacity: 0.5;
cursor: not-allowed;
filter: saturate(0);
}
.cta {
text-transform: uppercase;
font-weight: 700;
letter-spacing: 0.05em;
padding: 0.8rem 1rem;
}
.multi-state-button {
display: grid;
text-align: center;
align-items: center;
}
.multi-state-button > * {
grid-area: 1/1/2/2;
}
.multi-state-button button {
z-index: 1;
}
.multi-state-button sm-spinner {
justify-self: center;
}
sm-spinner {
--size: 1.5rem;
--stroke-width: 0.1rem;
}
sm-input,
sm-textarea {
--border-radius: 0.2rem;
--border-radius: 0.5rem;
--background: rgba(var(--text-color), 0.06);
}
sm-button {
--border-radius: 0.2rem;
.password-field label {
display: flex;
justify-content: center;
}
sm-tab-header {
align-self: flex-start;
.password-field label input:checked ~ .visible {
display: none;
}
.password-field label input:not(:checked) ~ .invisible {
display: none;
}
ul {
@ -145,6 +251,10 @@ ul {
display: flex;
}
.flex-wrap {
flex-wrap: wrap;
}
.grid {
display: grid;
}
@ -189,7 +299,7 @@ ul {
align-items: flex-start;
}
.align-center {
.align-items-center {
align-items: center;
}
@ -229,7 +339,7 @@ ul {
justify-self: end;
}
.direction-column {
.flex-direction-column {
flex-direction: column;
}
@ -273,6 +383,14 @@ ul {
margin-left: 0.5rem;
}
.margin-left-auto {
margin-left: auto;
}
.margin-right-0-3 {
margin-right: 0.3rem;
}
.margin-right-0-5 {
margin-right: 0.5rem;
}
@ -310,7 +428,7 @@ ul {
pointer-events: none;
}
.hide-completely {
.hidden {
display: none !important;
}
@ -326,10 +444,12 @@ ul {
}
.ripple {
height: 8rem;
width: 8rem;
position: absolute;
border-radius: 50%;
transform: scale(0);
background: rgba(var(--text-color), 0.16);
background: radial-gradient(circle, rgba(var(--text-color), 0.3) 0%, rgba(0, 0, 0, 0) 50%);
pointer-events: none;
}
@ -349,9 +469,35 @@ ul {
}
.icon {
width: 1.5rem;
height: 1.5rem;
fill: rgba(var(--text-color), 0.9);
height: 1.2rem;
width: 1.2rem;
fill: rgba(var(--text-color), 1);
flex-shrink: 0;
min-width: -webkit-max-content;
min-width: -moz-max-content;
min-width: max-content;
}
.popup__header {
position: relative;
display: grid;
gap: 0.5rem;
width: 100%;
padding: 0 1.5rem 0 0.5rem;
align-items: center;
grid-template-columns: auto 1fr;
}
.popup__header > * {
grid-row: 1;
}
.popup__header h3,
.popup__header h4 {
grid-column: 1/-1;
justify-self: center;
align-self: center;
}
.popup__header__close {
grid-column: 1;
}
#loading_page {
@ -378,10 +524,24 @@ ul {
}
.sad-face .eyes {
transform-origin: center;
animation: blink 1s infinite alternate;
-webkit-animation: blink 1s infinite alternate;
animation: blink 1s infinite alternate;
}
.sad-face .face {
animation: nod 2s 1s;
-webkit-animation: nod 2s 1s;
animation: nod 2s 1s;
}
@-webkit-keyframes blink {
0% {
transform: scaleY(1);
}
80% {
transform: scaleY(1);
}
100% {
transform: scaleY(0);
}
}
@keyframes blink {
@ -395,6 +555,38 @@ ul {
transform: scaleY(0);
}
}
@-webkit-keyframes nod {
0% {
transform: translateX(0);
}
20% {
transform: translateX(-1.5rem);
}
30% {
transform: translateX(1.5rem);
}
40% {
transform: translateX(-1rem);
}
50% {
transform: translateX(1rem);
}
60% {
transform: translateX(-0.7rem);
}
70% {
transform: translateX(0.7rem);
}
80% {
transform: translateX(-0.5rem);
}
90% {
transform: translateX(0.5rem);
}
100% {
transform: translateX(0);
}
}
@keyframes nod {
0% {
transform: translateX(0);
@ -439,21 +631,37 @@ ul {
fill: rgba(var(--text-color), 1);
}
#loader {
display: flex;
position: relative;
#main_loader {
width: 4rem;
height: 8rem;
}
.loader {
display: flex;
position: relative;
fill: none;
stroke-width: 4;
stroke-linecap: round;
stroke: rgba(var(--text-color), 1);
}
#loader polyline:nth-of-type(2),
#loader polyline:nth-of-type(3),
#loader polyline:nth-of-type(4) {
.loader polyline:nth-of-type(2),
.loader polyline:nth-of-type(3),
.loader polyline:nth-of-type(4) {
stroke-dasharray: 60;
animation: loading infinite 1s;
-webkit-animation: loading infinite 1s;
animation: loading infinite 1s;
}
@-webkit-keyframes loading {
0% {
stroke-dashoffset: -60;
}
50% {
stroke-dashoffset: 0;
}
100% {
stroke-dashoffset: 60;
}
}
@keyframes loading {
@ -473,26 +681,21 @@ ul {
gap: 1.5rem;
padding: 1rem;
align-items: center;
background-color: var(--accent-color);
background-color: #3f00ac;
color: white;
}
#main_header__logo {
fill: white;
height: 2.4rem;
width: 2.4rem;
margin-left: -0.3rem;
height: 1.5rem;
width: 1.5rem;
}
.header__company-name {
font-size: 1em;
font-weight: 500;
}
.header__app-name {
font-weight: 700;
font-size: 1.2rem;
}
#current_price {
justify-self: flex-start;
}
@ -510,7 +713,6 @@ ul {
}
sm-select {
min-width: 6rem;
--selection-font-size: 0.9rem;
}
@ -536,7 +738,7 @@ sm-option {
grid-template-columns: 1fr;
justify-content: flex-start;
color: rgba(var(--text-color), 1);
width: min(24rem, calc(100vw - 2rem));
width: min(24rem, 100vw - 2rem);
background-color: rgba(var(--foreground-color), 1);
box-shadow: 0 2rem 2rem -0.5rem rgba(0, 0, 0, 0.16);
}
@ -548,74 +750,139 @@ sm-option {
align-items: center;
justify-content: center;
border: none;
height: 2.2rem;
width: 2.2rem;
justify-self: flex-end;
border-radius: 50%;
padding: 0.5rem;
background-color: rgba(0, 0, 0, 0.16);
}
#profile_button .icon {
height: 1rem;
width: 1rem;
fill: white;
}
#theme_switcher {
overflow: hidden;
width: 100%;
}
.page-layout {
display: grid;
grid-template-columns: 1rem 1fr 1rem;
grid-template-columns: 1rem minmax(0, 1fr) 1rem;
}
.page-layout > * {
grid-column: 2/3;
}
#home_page {
--side-padding: 1rem;
display: flex;
flex-direction: column;
padding: 0 max(1rem, var(--side-padding));
padding-bottom: 6rem;
}
#homepage__hero-section {
color: white;
padding: 2rem 0 12rem 0;
margin-bottom: -8rem;
padding: 2rem var(--side-padding) 5rem var(--side-padding);
margin: 0 calc(-1 * max(1rem, var(--side-padding))) -1.6rem calc(-1 * max(1rem, var(--side-padding)));
background-image: url(../assets/bg-1.svg);
background-color: var(--accent-color);
background-color: #3f00ac;
background-size: cover;
}
#homepage__hero-section p {
color: rgba(255, 255, 255, 0.8);
color: rgba(255, 255, 255, 0.9);
}
.bond-list__header {
color: white;
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
#refresh_button {
color: var(--accent-color);
border: solid thin rgba(0, 0, 0, 0.2);
}
#search_bonds {
--font-weight: 500;
--padding: 0.5rem 1rem;
position: -webkit-sticky;
position: sticky;
top: 1rem;
--background: rgba(var(--foreground-color), 1);
border: solid thin rgba(var(--text-color), 0.2);
color: rgba(var(--text-color), 1);
grid-area: search;
width: min(24rem, 100%);
}
#search_bonds .icon {
height: 1.2rem;
width: 1.2rem;
margin-left: -0.5rem;
--padding: 1rem;
width: min(26rem, 100%);
align-self: center;
margin-bottom: 1.5rem;
border-radius: 0.5rem;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
z-index: 1;
}
#bond_list {
display: grid;
gap: 1rem;
padding: 1rem 0;
margin-bottom: 4rem;
align-content: flex-start;
}
#bond_list .loader {
height: 2rem;
width: 2rem;
justify-self: center;
margin-top: 4vw;
}
.bond-transaction {
display: grid;
align-items: flex-start;
width: 100%;
gap: 1rem;
padding: 1rem;
border-radius: 0.5rem;
background-color: rgba(var(--foreground-color), 1);
}
.bond-transaction > * {
flex-basis: max(8rem, 12vw);
}
.bond-transaction__withdraw {
background-color: var(--accent-color);
padding: 0.8rem 1.5rem;
color: rgba(var(--background-color), 1);
margin-left: auto;
}
.bond-transaction .tag {
display: inline-flex;
padding: 0.4rem 0.8rem;
border-radius: 0.5rem;
font-size: 0.8rem;
font-weight: 500;
color: var(--danger-color);
border: solid 0.1rem var(--danger-color);
}
.bond-transaction--closed a {
flex-basis: auto;
}
.label {
font-weight: 500;
font-size: 0.75rem;
margin-bottom: 0.2rem;
color: rgba(var(--text-color), 0.8);
}
.value {
font-weight: 600;
font-size: 0.9rem;
overflow-wrap: break-word;
word-wrap: break-word;
-ms-word-break: break-all;
word-break: break-word;
-webkit-hyphens: auto;
hyphens: auto;
}
.more-details {
border-top: solid thin rgba(var(--text-color), 0.2);
padding-top: 1rem;
}
#bond_list__empty-state {
padding: 4rem 0;
margin-top: -5rem;
border-radius: 0.3rem;
place-content: center;
margin-bottom: 3rem;
@ -628,54 +895,16 @@ sm-option {
justify-self: center;
}
bond-transaction {
box-shadow: 0 1rem 2rem -1rem rgba(0, 0, 0, 0.16);
}
.flex-grid {
display: grid;
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: start;
grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
}
.flex-grid > * {
flex-basis: max(8rem, 12vw);
}
.bond-placeholder {
gap: 1rem;
padding: 1rem;
border-radius: 0.3rem;
background-color: rgba(var(--foreground-color), 1);
box-shadow: 0 1rem 2rem -1rem rgba(0, 0, 0, 0.16);
}
.bond-placeholder .placeholder__block:first-of-type {
width: min(24rem, 100%);
}
.bond-placeholder:nth-of-type(2) .placeholder__block {
animation-delay: 0.3s;
}
.bond-placeholder:nth-of-type(3) .placeholder__block {
animation-delay: 0.6s;
}
.bond-placeholder:nth-of-type(4) .placeholder__block {
animation-delay: 0.8s;
}
.placeholder__block {
display: flex;
border-radius: 0.3rem;
min-width: 9rem;
padding: 1.2rem 1rem;
animation: pulse alternate 0.6s ease infinite;
background-color: rgba(var(--text-color), 0.1);
}
@keyframes pulse {
from {
opacity: 0.4;
}
to {
opacity: 1;
}
}
form {
padding: 1rem;
border-radius: 0.3rem;
@ -700,18 +929,6 @@ form select option {
#bond_details {
line-height: 1.7;
margin-bottom: 1.5rem;
overflow-wrap: break-word;
word-wrap: break-word;
-ms-word-break: break-all;
/* This is the dangerous one in WebKit, as it breaks things wherever */
word-break: break-all;
/* Instead use this non-standard one: */
word-break: break-word;
/* Adds a hyphen where the word breaks, if supported (No Blink) */
-ms-hyphens: auto;
-moz-hyphens: auto;
-webkit-hyphens: auto;
hyphens: auto;
}
#admin_id {
@ -722,75 +939,112 @@ form select option {
margin-left: -0.8rem;
}
.user-action-result__icon {
justify-self: center;
height: 4rem;
width: 4rem;
border-radius: 5rem;
margin-bottom: 2rem;
-webkit-animation: popup 1s;
animation: popup 1s;
}
.user-action-result__icon.success {
fill: rgba(var(--background-color), 1);
padding: 1rem;
background-color: #0bbe56;
}
.user-action-result__icon.failed {
background-color: rgba(var(--text-color), 0.03);
fill: var(--danger-color);
}
@-webkit-keyframes popup {
0% {
opacity: 0;
transform: scale(0.2) translateY(600%);
}
10% {
transform: scale(0.2) translateY(5rem);
opacity: 1;
}
40% {
transform: scale(0.2) translateY(0);
}
80% {
transform: scale(1.1) translateY(0);
}
100% {
transform: scale(1) translateY(0);
}
}
@keyframes popup {
0% {
opacity: 0;
transform: scale(0.2) translateY(600%);
}
10% {
transform: scale(0.2) translateY(5rem);
opacity: 1;
}
40% {
transform: scale(0.2) translateY(0);
}
80% {
transform: scale(1.1) translateY(0);
}
100% {
transform: scale(1) translateY(0);
}
}
@media only screen and (max-width: 640px) {
.h1 {
font-size: 4rem;
}
.h2 {
font-size: 3rem;
}
.h3 {
font-size: 2rem;
}
.h2 {
font-size: 1.8rem;
}
.h3 {
font-size: 1.3rem;
}
.h4 {
font-size: 1rem;
font-size: 1.5rem;
}
.h5 {
font-size: 0.8rem;
}
#main_header {
grid-template-areas: ". profile-button";
}
#main_header .dropdown {
grid-area: profile-button;
}
#homepage__hero-section {
padding: 2rem 0 20rem 0;
margin-bottom: -16rem;
}
.bond-list__header {
grid-template-columns: auto 1fr;
grid-template-areas: ". ." "search search";
}
}
@media only screen and (min-width: 640px) {
sm-popup {
--width: 26rem;
}
.popup__header {
padding: 1.5rem 1.5rem 0 0.75rem;
}
#home_page {
--side-padding: 8vw;
}
#main_header {
padding: 2rem calc((10vw / 2) - 0.4rem);
padding: 2rem calc(5vw - 0.4rem);
grid-template-columns: auto 1fr auto;
}
#main_header::after {
height: 5rem;
}
.page-layout {
grid-template-columns: 1fr 90vw 1fr;
}
#current_price {
justify-self: flex-end;
}
.dropdown__panel {
left: auto;
}
.bond-list__header {
grid-template-columns: auto 1fr auto;
grid-template-areas: ". search .";
}
.bond-placeholder {
grid-template-columns: 1fr auto;
}
.bond-placeholder > .placeholder__block:nth-of-type(2) {
justify-self: flex-end;
.bond-transaction {
padding: 1.5rem;
}
}
@media only screen and (min-width: 1280px) {
@ -803,7 +1057,6 @@ form select option {
width: 0.5rem;
height: 0.5rem;
}
::-webkit-scrollbar-thumb {
background: rgba(var(--text-color), 0.3);
border-radius: 1rem;

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

11029
index.html

File diff suppressed because it is too large Load Diff

217
scripts/bonds.js Normal file
View File

@ -0,0 +1,217 @@
const blockchainBond = (function () {
const productStr = "Product: RanchiMall Bitcoin Bond";
const magnitude = m => {
switch (m) {
case "thousand": return 1000;
case "lakh": case "lakhs": return 100000;
case "million": return 1000000;
case "crore": case "crores": return 10000000;
default: return null;
}
}
const parseNumber = (str) => {
let n = 0,
g = 0;
str.toLowerCase().replace(/,/g, '').split(" ").forEach(s => {
if (!isNaN(s))
g = parseFloat(s);
else {
let m = magnitude(s);
if (m !== null) {
n += m * g;
g = 0;
}
}
});
return n + g;
}
const parsePeriod = (str) => {
let P = '', n = 0;
str.toLowerCase().replace(/,/g, '').split(" ").forEach(s => {
if (!isNaN(s))
n = parseFloat(s);
else switch (s) {
case "year(s)": case "year": case "years": P += (n + 'Y'); n = 0; break;
case "month(s)": case "month": case "months": P += (n + 'M'); n = 0; break;
case "day(s)": case "day": case "days": P += (n + 'D'); n = 0; break;
}
});
return P;
}
const dateFormat = (date = null) => {
let d = (date ? new Date(date) : new Date()).toDateString();
return [d.substring(8, 10), d.substring(4, 7), d.substring(11, 15)].join(" ");
}
const yearDiff = (d1 = null, d2 = null) => {
d1 = d1 ? new Date(d1) : new Date();
d2 = d2 ? new Date(d2) : new Date();
let y = d1.getYear() - d2.getYear(),
m = d1.getMonth() - d2.getMonth(),
d = d1.getDate() - d2.getDate()
return y + m / 12 + d / 365;
}
const dateAdder = function (start_date, duration) {
let date = new Date(start_date);
let y = parseInt(duration.match(/\d+Y/)),
m = parseInt(duration.match(/\d+M/)),
d = parseInt(duration.match(/\d+D/));
if (!isNaN(y))
date.setFullYear(date.getFullYear() + y);
if (!isNaN(m))
date.setMonth(date.getMonth() + m);
if (!isNaN(d))
date.setDate(date.getDate() + d);
return date;
}
function calcNetValue(BTC_base, BTC_net, startDate, minIpa, maxPeriod, cut, amount, USD_base, USD_net) {
let gain, duration, interest, net;
gain = (BTC_net - BTC_base) / BTC_base;
duration = yearDiff(Math.min(Date.now(), dateAdder(startDate, maxPeriod).getTime()), startDate);
interest = Math.max(cut * gain, minIpa * duration);
net = amount / USD_base;
net += net * interest;
return net * USD_net;
}
function stringify_main(BTC_base, start_date, guaranteed_interest, guarantee_period, gain_cut, amount, USD_base, lockin_period, floID) {
return [
`${productStr}`,
`Base value: ${BTC_base} USD`,
`Date of bond start: ${dateFormat(start_date)}`,
`Guaranteed interest: ${guaranteed_interest}% per annum simple for ${guarantee_period}`,
`Bond value: guaranteed interest or ${gain_cut}% of the gains whichever is higher`,
`Amount invested: Rs ${amount}`,
`USD INR rate at start: ${USD_base}`,
`Lockin period: ${lockin_period}`,
`FLO ID of Bond Holder: ${floID}`
].join("|");
}
function parse_main(data) {
//Data (add bond) sent by admin
let details = {};
data.split("|").forEach(d => {
d = d.split(': ');
switch (d[0].toLowerCase()) {
case "base value":
details["BTC_base"] = parseNumber(d[1].slice(0, -4)); break;
case "date of bond start":
details["startDate"] = new Date(d[1]); break;
case "guaranteed interest":
details["minIpa"] = parseFloat(d[1].match(/\d+%/)) / 100;
details["maxPeriod"] = parsePeriod(d[1].match(/for .+/).toString()); break;
case "bond value":
details["cut"] = parseFloat(d[1].match(/\d+%/)) / 100; break;
case "amount invested":
details["amount"] = parseNumber(d[1].substring(3)); break;
case "usd inr rate at start":
details["USD_base"] = parseFloat(d[1]); break;
case "lockin period":
details["lockinPeriod"] = parsePeriod(d[1]); break;
case "flo id of bond holder":
details["floID"] = d[1]; break;
}
});
return details;
}
function stringify_end(bond_id, end_date, BTC_net, USD_net, amount, ref_sign, payment_ref) {
return [
`${productStr}`,
`Bond: ${bond_id}`,
`End value: ${BTC_net} USD`,
`Date of bond end: ${dateFormat(end_date)}`,
`USD INR rate at end: ${USD_net}`,
`Amount withdrawn: Rs ${amount} via ${payment_ref}`,
`Reference: ${ref_sign}`
].join("|");
}
function parse_end(data) {
//Data (end bond) send by market nodes
let details = {};
data.split("|").forEach(d => {
d = d.split(': ');
switch (d[0].toLowerCase()) {
case "bond":
details["bondID"] = d[1]; break;
case "end value":
details["BTC_net"] = parseNumber(d[1].slice(0, -4)); break;
case "date of bond end":
details["endDate"] = new Date(d[1]); break;
case "amount withdrawn":
details["amountFinal"] = parseNumber(d[1].match(/\d.+ via/).toString());
details["payment_refRef"] = d[1].match(/via .+/).toString().substring(4); break;
case "usd inr rate at end":
details["USD_net"] = parseFloat(d[1]); break;
case "reference":
details["refSign"] = d[1]; break;
}
});
return details;
}
return {
productStr,
dateAdder,
dateFormat,
calcNetValue,
parse: {
main: parse_main,
end: parse_end
},
stringify: {
main: stringify_main,
end: stringify_end
}
}
})();
function refreshBlockchainData() {
return new Promise((resolve, reject) => {
compactIDB.readData("appendix", "lastTx" + floGlobals.adminID).then(lastTx => {
floBlockchainAPI.readData(floGlobals.adminID, {
ignoreOld: lastTx,
senders: floExchangeAPI.nodeList.concat(floGlobals.adminID), //sentOnly: true,
tx: true,
filter: d => d.startsWith(blockchainBond.productStr)
}).then(result => {
result.data.reverse().forEach(d => {
if (d.senders.has(floGlobals.adminID) && /bond start/i.test(d.data))
compactIDB.addData('bonds', d.data, d.txid);
else
compactIDB.addData('closings', d.data, d.txid);
});
compactIDB.writeData('appendix', result.totalTxs, "lastTx" + floGlobals.adminID);
resolve(true);
}).catch(error => reject(error))
}).catch(error => reject(error))
})
}
function getCurrentRates() {
let fetchData = api => new Promise((resolve, reject) => {
fetch(api).then(response => {
if (response.ok)
response.json().then(data => resolve(data))
else
reject(response)
}).catch(error => reject(error))
})
return new Promise((resolve, reject) => {
fetchData(`https://bitpay.com/api/rates`).then(result => {
let BTC_USD, BTC_INR, USD_INR
for (let i of result)
i.code == "USD" ? BTC_USD = i.rate : i.code == "INR" ? BTC_INR = i.rate : null;
USD_INR = BTC_INR / BTC_USD;
resolve({
BTC_USD,
BTC_INR,
USD_INR
})
}).catch(error => reject(error))
})
}

259
scripts/compactIDB.js Normal file
View File

@ -0,0 +1,259 @@
(function(EXPORTS) { //compactIDB v2.1.0
/* Compact IndexedDB operations */
'use strict';
const compactIDB = EXPORTS;
var defaultDB;
const indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
const IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
const IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
if (!indexedDB) {
console.error("Your browser doesn't support a stable version of IndexedDB.");
return;
}
compactIDB.setDefaultDB = dbName => defaultDB = dbName;
Object.defineProperty(compactIDB, 'default', {
get: () => defaultDB,
set: dbName => defaultDB = dbName
});
function getDBversion(dbName = defaultDB) {
return new Promise((resolve, reject) => {
openDB(dbName).then(db => {
resolve(db.version)
db.close()
}).catch(error => reject(error))
})
}
function upgradeDB(dbName, createList = null, deleteList = null) {
return new Promise((resolve, reject) => {
getDBversion(dbName).then(version => {
var idb = indexedDB.open(dbName, version + 1);
idb.onerror = (event) => reject("Error in opening IndexedDB");
idb.onupgradeneeded = (event) => {
let db = event.target.result;
if (createList instanceof Object) {
if (Array.isArray(createList)) {
let tmp = {}
createList.forEach(o => tmp[o] = {})
createList = tmp
}
for (let o in createList) {
let obs = db.createObjectStore(o, createList[o].options || {});
if (createList[o].indexes instanceof Object)
for (let i in createList[o].indexes)
obs.createIndex(i, i, createList[o].indexes || {});
}
}
if (Array.isArray(deleteList))
deleteList.forEach(o => db.deleteObjectStore(o));
resolve('Database upgraded')
}
idb.onsuccess = (event) => event.target.result.close();
}).catch(error => reject(error))
})
}
compactIDB.initDB = function(dbName, objectStores = {}) {
return new Promise((resolve, reject) => {
if (!(objectStores instanceof Object))
return reject('ObjectStores must be an object or array')
defaultDB = defaultDB || dbName;
var idb = indexedDB.open(dbName);
idb.onerror = (event) => reject("Error in opening IndexedDB");
idb.onsuccess = (event) => {
var db = event.target.result;
let cList = Object.values(db.objectStoreNames);
var obs = {},
a_obs = {},
d_obs = [];
if (!Array.isArray(objectStores))
var obs = objectStores
else
objectStores.forEach(o => obs[o] = {})
let nList = Object.keys(obs)
for (let o of nList)
if (!cList.includes(o))
a_obs[o] = obs[o]
for (let o of cList)
if (!nList.includes(o))
d_obs.push(o)
if (!Object.keys(a_obs).length && !d_obs.length)
resolve("Initiated IndexedDB");
else
upgradeDB(dbName, a_obs, d_obs)
.then(result => resolve(result))
.catch(error => reject(error))
db.close();
}
});
}
const openDB = compactIDB.openDB = function(dbName = defaultDB) {
return new Promise((resolve, reject) => {
var idb = indexedDB.open(dbName);
idb.onerror = (event) => reject("Error in opening IndexedDB");
idb.onupgradeneeded = (event) => {
event.target.result.close();
deleteDB(dbName).then(_ => null).catch(_ => null).finally(_ => reject("Datebase not found"))
}
idb.onsuccess = (event) => resolve(event.target.result);
});
}
const deleteDB = compactIDB.deleteDB = function(dbName = defaultDB) {
return new Promise((resolve, reject) => {
var deleteReq = indexedDB.deleteDatabase(dbName);;
deleteReq.onerror = (event) => reject("Error deleting database!");
deleteReq.onsuccess = (event) => resolve("Database deleted successfully");
});
}
compactIDB.writeData = function(obsName, data, key = false, dbName = defaultDB) {
return new Promise((resolve, reject) => {
openDB(dbName).then(db => {
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
let writeReq = (key ? obs.put(data, key) : obs.put(data));
writeReq.onsuccess = (evt) => resolve(`Write data Successful`);
writeReq.onerror = (evt) => reject(
`Write data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
);
db.close();
}).catch(error => reject(error));
});
}
compactIDB.addData = function(obsName, data, key = false, dbName = defaultDB) {
return new Promise((resolve, reject) => {
openDB(dbName).then(db => {
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
let addReq = (key ? obs.add(data, key) : obs.add(data));
addReq.onsuccess = (evt) => resolve(`Add data successful`);
addReq.onerror = (evt) => reject(
`Add data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
);
db.close();
}).catch(error => reject(error));
});
}
compactIDB.removeData = function(obsName, key, dbName = defaultDB) {
return new Promise((resolve, reject) => {
openDB(dbName).then(db => {
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
let delReq = obs.delete(key);
delReq.onsuccess = (evt) => resolve(`Removed Data ${key}`);
delReq.onerror = (evt) => reject(
`Remove data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
);
db.close();
}).catch(error => reject(error));
});
}
compactIDB.clearData = function(obsName, dbName = defaultDB) {
return new Promise((resolve, reject) => {
openDB(dbName).then(db => {
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
let clearReq = obs.clear();
clearReq.onsuccess = (evt) => resolve(`Clear data Successful`);
clearReq.onerror = (evt) => reject(`Clear data Unsuccessful`);
db.close();
}).catch(error => reject(error));
});
}
compactIDB.readData = function(obsName, key, dbName = defaultDB) {
return new Promise((resolve, reject) => {
openDB(dbName).then(db => {
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
let getReq = obs.get(key);
getReq.onsuccess = (evt) => resolve(evt.target.result);
getReq.onerror = (evt) => reject(
`Read data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
);
db.close();
}).catch(error => reject(error));
});
}
compactIDB.readAllData = function(obsName, dbName = defaultDB) {
return new Promise((resolve, reject) => {
openDB(dbName).then(db => {
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
var tmpResult = {}
let curReq = obs.openCursor();
curReq.onsuccess = (evt) => {
var cursor = evt.target.result;
if (cursor) {
tmpResult[cursor.primaryKey] = cursor.value;
cursor.continue();
} else
resolve(tmpResult);
}
curReq.onerror = (evt) => reject(
`Read-All data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
);
db.close();
}).catch(error => reject(error));
});
}
/* compactIDB.searchData = function (obsName, options = {}, dbName = defaultDB) {
return new Promise((resolve, reject) => {
openDB(dbName).then(db => {
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
var filteredResult = {}
let keyRange;
if(options.lowerKey!==null && options.upperKey!==null)
keyRange = IDBKeyRange.bound(options.lowerKey, options.upperKey);
else if(options.lowerKey!==null)
keyRange = IDBKeyRange.lowerBound(options.lowerKey);
else if (options.upperKey!==null)
keyRange = IDBKeyRange.upperBound(options.upperBound);
else if (options.atKey)
let curReq = obs.openCursor(keyRange, )
}).catch(error => reject(error))
})
}*/
compactIDB.searchData = function(obsName, options = {}, dbName = defaultDB) {
options.lowerKey = options.atKey || options.lowerKey || 0
options.upperKey = options.atKey || options.upperKey || false
options.patternEval = options.patternEval || ((k, v) => {
return true
})
options.limit = options.limit || false;
options.lastOnly = options.lastOnly || false
return new Promise((resolve, reject) => {
openDB(dbName).then(db => {
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
var filteredResult = {}
let curReq = obs.openCursor(
options.upperKey ? IDBKeyRange.bound(options.lowerKey, options.upperKey) : IDBKeyRange.lowerBound(options.lowerKey),
options.lastOnly ? "prev" : "next");
curReq.onsuccess = (evt) => {
var cursor = evt.target.result;
if (cursor) {
if (options.patternEval(cursor.primaryKey, cursor.value)) {
filteredResult[cursor.primaryKey] = cursor.value;
options.lastOnly ? resolve(filteredResult) : cursor.continue();
} else
cursor.continue();
} else
resolve(filteredResult);
}
curReq.onerror = (evt) => reject(`Search unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`);
db.close();
}).catch(error => reject(error));
});
}
})(window.compactIDB = {});

8
scripts/components.min.js vendored Normal file

File diff suppressed because one or more lines are too long

580
scripts/floBlockchainAPI.js Normal file
View File

@ -0,0 +1,580 @@
(function (EXPORTS) { //floBlockchainAPI v2.3.3d
/* FLO Blockchain Operator to send/receive data from blockchain using API calls*/
'use strict';
const floBlockchainAPI = EXPORTS;
const DEFAULT = {
blockchain: floGlobals.blockchain,
apiURL: {
FLO: ['https://flosight.duckdns.org/'],
FLO_TEST: ['https://testnet-flosight.duckdns.org', 'https://testnet.flocha.in/']
},
sendAmt: 0.001,
fee: 0.0005,
minChangeAmt: 0.0005,
receiverID: floGlobals.adminID
};
Object.defineProperties(floBlockchainAPI, {
sendAmt: {
get: () => DEFAULT.sendAmt,
set: amt => !isNaN(amt) ? DEFAULT.sendAmt = amt : null
},
fee: {
get: () => DEFAULT.fee,
set: fee => !isNaN(fee) ? DEFAULT.fee = fee : null
},
defaultReceiver: {
get: () => DEFAULT.receiverID,
set: floID => DEFAULT.receiverID = floID
},
blockchain: {
get: () => DEFAULT.blockchain
}
});
if (floGlobals.sendAmt) floBlockchainAPI.sendAmt = floGlobals.sendAmt;
if (floGlobals.fee) floBlockchainAPI.fee = floGlobals.fee;
Object.defineProperties(floGlobals, {
sendAmt: {
get: () => DEFAULT.sendAmt,
set: amt => !isNaN(amt) ? DEFAULT.sendAmt = amt : null
},
fee: {
get: () => DEFAULT.fee,
set: fee => !isNaN(fee) ? DEFAULT.fee = fee : null
}
});
const allServerList = new Set(floGlobals.apiURL && floGlobals.apiURL[DEFAULT.blockchain] ? floGlobals.apiURL[DEFAULT.blockchain] : DEFAULT.apiURL[DEFAULT.blockchain]);
var serverList = Array.from(allServerList);
var curPos = floCrypto.randInt(0, serverList.length - 1);
function fetch_retry(apicall, rm_flosight) {
return new Promise((resolve, reject) => {
let i = serverList.indexOf(rm_flosight)
if (i != -1) serverList.splice(i, 1);
curPos = floCrypto.randInt(0, serverList.length - 1);
fetch_api(apicall, false)
.then(result => resolve(result))
.catch(error => reject(error));
})
}
function fetch_api(apicall, ic = true) {
return new Promise((resolve, reject) => {
if (serverList.length === 0) {
if (ic) {
serverList = Array.from(allServerList);
curPos = floCrypto.randInt(0, serverList.length - 1);
fetch_api(apicall, false)
.then(result => resolve(result))
.catch(error => reject(error));
} else
reject("No floSight server working");
} else {
let flosight = serverList[curPos];
fetch(flosight + apicall).then(response => {
if (response.ok)
response.json().then(data => resolve(data));
else {
fetch_retry(apicall, flosight)
.then(result => resolve(result))
.catch(error => reject(error));
}
}).catch(error => {
fetch_retry(apicall, flosight)
.then(result => resolve(result))
.catch(error => reject(error));
})
}
})
}
Object.defineProperties(floBlockchainAPI, {
serverList: {
get: () => Array.from(serverList)
},
current_server: {
get: () => serverList[curPos]
}
});
//Promised function to get data from API
const promisedAPI = floBlockchainAPI.promisedAPI = floBlockchainAPI.fetch = function (apicall) {
return new Promise((resolve, reject) => {
//console.log(apicall);
fetch_api(apicall)
.then(result => resolve(result))
.catch(error => reject(error));
});
}
//Get balance for the given Address
const getBalance = floBlockchainAPI.getBalance = function (addr) {
return new Promise((resolve, reject) => {
promisedAPI(`api/addr/${addr}/balance`)
.then(balance => resolve(parseFloat(balance)))
.catch(error => reject(error));
});
}
//Send Tx to blockchain
const sendTx = floBlockchainAPI.sendTx = function (senderAddr, receiverAddr, sendAmt, privKey, floData = '', strict_utxo = true) {
return new Promise((resolve, reject) => {
if (!floCrypto.validateASCII(floData))
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
else if (!floCrypto.validateFloID(senderAddr))
return reject(`Invalid address : ${senderAddr}`);
else if (!floCrypto.validateFloID(receiverAddr))
return reject(`Invalid address : ${receiverAddr}`);
else if (privKey.length < 1 || !floCrypto.verifyPrivKey(privKey, senderAddr))
return reject("Invalid Private key!");
else if (typeof sendAmt !== 'number' || sendAmt <= 0)
return reject(`Invalid sendAmt : ${sendAmt}`);
getBalance(senderAddr).then(balance => {
var fee = DEFAULT.fee;
if (balance < sendAmt + fee)
return reject("Insufficient FLO balance!");
//get unconfirmed tx list
promisedAPI(`api/addr/${senderAddr}`).then(result => {
readTxs(senderAddr, 0, result.unconfirmedTxApperances).then(result => {
let unconfirmedSpent = {};
for (let tx of result.items)
if (tx.confirmations == 0)
for (let vin of tx.vin)
if (vin.addr === senderAddr) {
if (Array.isArray(unconfirmedSpent[vin.txid]))
unconfirmedSpent[vin.txid].push(vin.vout);
else
unconfirmedSpent[vin.txid] = [vin.vout];
}
//get utxos list
promisedAPI(`api/addr/${senderAddr}/utxo`).then(utxos => {
//form/construct the transaction data
var trx = bitjs.transaction();
var utxoAmt = 0.0;
for (var i = utxos.length - 1;
(i >= 0) && (utxoAmt < sendAmt + fee); i--) {
//use only utxos with confirmations (strict_utxo mode)
if (utxos[i].confirmations || !strict_utxo) {
if (utxos[i].txid in unconfirmedSpent && unconfirmedSpent[utxos[i].txid].includes(utxos[i].vout))
continue; //A transaction has already used the utxo, but is unconfirmed.
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
utxoAmt += utxos[i].amount;
};
}
if (utxoAmt < sendAmt + fee)
reject("Insufficient FLO: Some UTXOs are unconfirmed");
else {
trx.addoutput(receiverAddr, sendAmt);
var change = utxoAmt - sendAmt - fee;
if (change > DEFAULT.minChangeAmt)
trx.addoutput(senderAddr, change);
trx.addflodata(floData.replace(/\n/g, ' '));
var signedTxHash = trx.sign(privKey, 1);
broadcastTx(signedTxHash)
.then(txid => resolve(txid))
.catch(error => reject(error))
}
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
});
}
//Write Data into blockchain
floBlockchainAPI.writeData = function (senderAddr, data, privKey, receiverAddr = DEFAULT.receiverID, options = {}) {
let strict_utxo = options.strict_utxo === false ? false : true,
sendAmt = isNaN(options.sendAmt) ? DEFAULT.sendAmt : options.sendAmt;
return new Promise((resolve, reject) => {
if (typeof data != "string")
data = JSON.stringify(data);
sendTx(senderAddr, receiverAddr, sendAmt, privKey, data, strict_utxo)
.then(txid => resolve(txid))
.catch(error => reject(error));
});
}
//merge all UTXOs of a given floID into a single UTXO
floBlockchainAPI.mergeUTXOs = function (floID, privKey, floData = '') {
return new Promise((resolve, reject) => {
if (!floCrypto.validateFloID(floID))
return reject(`Invalid floID`);
if (!floCrypto.verifyPrivKey(privKey, floID))
return reject("Invalid Private Key");
if (!floCrypto.validateASCII(floData))
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
var trx = bitjs.transaction();
var utxoAmt = 0.0;
var fee = DEFAULT.fee;
promisedAPI(`api/addr/${floID}/utxo`).then(utxos => {
for (var i = utxos.length - 1; i >= 0; i--)
if (utxos[i].confirmations) {
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
utxoAmt += utxos[i].amount;
}
trx.addoutput(floID, utxoAmt - fee);
trx.addflodata(floData.replace(/\n/g, ' '));
var signedTxHash = trx.sign(privKey, 1);
broadcastTx(signedTxHash)
.then(txid => resolve(txid))
.catch(error => reject(error))
}).catch(error => reject(error))
})
}
/**Write data into blockchain from (and/or) to multiple floID
* @param {Array} senderPrivKeys List of sender private-keys
* @param {string} data FLO data of the txn
* @param {Array} receivers List of receivers
* @param {boolean} preserveRatio (optional) preserve ratio or equal contribution
* @return {Promise}
*/
floBlockchainAPI.writeDataMultiple = function (senderPrivKeys, data, receivers = [DEFAULT.receiverID], preserveRatio = true) {
return new Promise((resolve, reject) => {
if (!Array.isArray(senderPrivKeys))
return reject("Invalid senderPrivKeys: SenderPrivKeys must be Array");
if (!preserveRatio) {
let tmp = {};
let amount = (DEFAULT.sendAmt * receivers.length) / senderPrivKeys.length;
senderPrivKeys.forEach(key => tmp[key] = amount);
senderPrivKeys = tmp;
}
if (!Array.isArray(receivers))
return reject("Invalid receivers: Receivers must be Array");
else {
let tmp = {};
let amount = DEFAULT.sendAmt;
receivers.forEach(floID => tmp[floID] = amount);
receivers = tmp
}
if (typeof data != "string")
data = JSON.stringify(data);
sendTxMultiple(senderPrivKeys, receivers, data)
.then(txid => resolve(txid))
.catch(error => reject(error))
})
}
/**Send Tx from (and/or) to multiple floID
* @param {Array or Object} senderPrivKeys List of sender private-key (optional: with coins to be sent)
* @param {Object} receivers List of receivers with respective amount to be sent
* @param {string} floData FLO data of the txn
* @return {Promise}
*/
const sendTxMultiple = floBlockchainAPI.sendTxMultiple = function (senderPrivKeys, receivers, floData = '') {
return new Promise((resolve, reject) => {
if (!floCrypto.validateASCII(floData))
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
let senders = {},
preserveRatio;
//check for argument validations
try {
let invalids = {
InvalidSenderPrivKeys: [],
InvalidSenderAmountFor: [],
InvalidReceiverIDs: [],
InvalidReceiveAmountFor: []
}
let inputVal = 0,
outputVal = 0;
//Validate sender privatekeys (and send amount if passed)
//conversion when only privateKeys are passed (preserveRatio mode)
if (Array.isArray(senderPrivKeys)) {
senderPrivKeys.forEach(key => {
try {
if (!key)
invalids.InvalidSenderPrivKeys.push(key);
else {
let floID = floCrypto.getFloID(key);
senders[floID] = {
wif: key
}
}
} catch (error) {
invalids.InvalidSenderPrivKeys.push(key)
}
})
preserveRatio = true;
}
//conversion when privatekeys are passed with send amount
else {
for (let key in senderPrivKeys) {
try {
if (!key)
invalids.InvalidSenderPrivKeys.push(key);
else {
if (typeof senderPrivKeys[key] !== 'number' || senderPrivKeys[key] <= 0)
invalids.InvalidSenderAmountFor.push(key);
else
inputVal += senderPrivKeys[key];
let floID = floCrypto.getFloID(key);
senders[floID] = {
wif: key,
coins: senderPrivKeys[key]
}
}
} catch (error) {
invalids.InvalidSenderPrivKeys.push(key)
}
}
preserveRatio = false;
}
//Validate the receiver IDs and receive amount
for (let floID in receivers) {
if (!floCrypto.validateFloID(floID))
invalids.InvalidReceiverIDs.push(floID);
if (typeof receivers[floID] !== 'number' || receivers[floID] <= 0)
invalids.InvalidReceiveAmountFor.push(floID);
else
outputVal += receivers[floID];
}
//Reject if any invalids are found
for (let i in invalids)
if (!invalids[i].length)
delete invalids[i];
if (Object.keys(invalids).length)
return reject(invalids);
//Reject if given inputVal and outputVal are not equal
if (!preserveRatio && inputVal != outputVal)
return reject(`Input Amount (${inputVal}) not equal to Output Amount (${outputVal})`);
} catch (error) {
return reject(error)
}
//Get balance of senders
let promises = [];
for (let floID in senders)
promises.push(getBalance(floID));
Promise.all(promises).then(results => {
let totalBalance = 0,
totalFee = DEFAULT.fee,
balance = {};
//Divide fee among sender if not for preserveRatio
if (!preserveRatio)
var dividedFee = totalFee / Object.keys(senders).length;
//Check if balance of each sender is sufficient enough
let insufficient = [];
for (let floID in senders) {
balance[floID] = parseFloat(results.shift());
if (isNaN(balance[floID]) || (preserveRatio && balance[floID] <= totalFee) ||
(!preserveRatio && balance[floID] < senders[floID].coins + dividedFee))
insufficient.push(floID);
totalBalance += balance[floID];
}
if (insufficient.length)
return reject({
InsufficientBalance: insufficient
})
//Calculate totalSentAmount and check if totalBalance is sufficient
let totalSendAmt = totalFee;
for (let floID in receivers)
totalSendAmt += receivers[floID];
if (totalBalance < totalSendAmt)
return reject("Insufficient total Balance");
//Get the UTXOs of the senders
let promises = [];
for (let floID in senders)
promises.push(promisedAPI(`api/addr/${floID}/utxo`));
Promise.all(promises).then(results => {
let wifSeq = [];
var trx = bitjs.transaction();
for (let floID in senders) {
let utxos = results.shift();
let sendAmt;
if (preserveRatio) {
let ratio = (balance[floID] / totalBalance);
sendAmt = totalSendAmt * ratio;
} else
sendAmt = senders[floID].coins + dividedFee;
let wif = senders[floID].wif;
let utxoAmt = 0.0;
for (let i = utxos.length - 1;
(i >= 0) && (utxoAmt < sendAmt); i--) {
if (utxos[i].confirmations) {
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
wifSeq.push(wif);
utxoAmt += utxos[i].amount;
}
}
if (utxoAmt < sendAmt)
return reject("Insufficient balance:" + floID);
let change = (utxoAmt - sendAmt);
if (change > 0)
trx.addoutput(floID, change);
}
for (let floID in receivers)
trx.addoutput(floID, receivers[floID]);
trx.addflodata(floData.replace(/\n/g, ' '));
for (let i = 0; i < wifSeq.length; i++)
trx.signinput(i, wifSeq[i], 1);
var signedTxHash = trx.serialize();
broadcastTx(signedTxHash)
.then(txid => resolve(txid))
.catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
})
}
//Broadcast signed Tx in blockchain using API
const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) {
return new Promise((resolve, reject) => {
if (signedTxHash.length < 1)
return reject("Empty Signature");
var url = serverList[curPos] + 'api/tx/send';
fetch(url, {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: `{"rawtx":"${signedTxHash}"}`
}).then(response => {
if (response.ok)
response.json().then(data => resolve(data.txid.result));
else
response.text().then(data => resolve(data));
}).catch(error => reject(error));
})
}
floBlockchainAPI.getTx = function (txid) {
return new Promise((resolve, reject) => {
promisedAPI(`api/tx/${txid}`)
.then(response => resolve(response))
.catch(error => reject(error))
})
}
//Read Txs of Address between from and to
const readTxs = floBlockchainAPI.readTxs = function (addr, from, to) {
return new Promise((resolve, reject) => {
promisedAPI(`api/addrs/${addr}/txs?from=${from}&to=${to}`)
.then(response => resolve(response))
.catch(error => reject(error))
});
}
//Read All Txs of Address (newest first)
floBlockchainAPI.readAllTxs = function (addr) {
return new Promise((resolve, reject) => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${response.totalItems}0`)
.then(response => resolve(response.items))
.catch(error => reject(error));
}).catch(error => reject(error))
});
}
/*Read flo Data from txs of given Address
options can be used to filter data
limit : maximum number of filtered data (default = 1000, negative = no limit)
ignoreOld : ignore old txs (default = 0)
sentOnly : filters only sent data
receivedOnly: filters only received data
pattern : filters data that with JSON pattern
filter : custom filter funtion for floData (eg . filter: d => {return d[0] == '$'})
tx : (boolean) resolve tx data or not (resolves an Array of Object with tx details)
sender : flo-id(s) of sender
receiver : flo-id(s) of receiver
*/
floBlockchainAPI.readData = function (addr, options = {}) {
options.limit = options.limit || 0;
options.ignoreOld = options.ignoreOld || 0;
if (typeof options.senders === "string") options.senders = [options.senders];
if (typeof options.receivers === "string") options.receivers = [options.receivers];
return new Promise((resolve, reject) => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
var newItems = response.totalItems - options.ignoreOld;
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${newItems * 2}`).then(response => {
if (options.limit <= 0)
options.limit = response.items.length;
var filteredData = [];
let numToRead = response.totalItems - options.ignoreOld,
unconfirmedCount = 0;
for (let i = 0; i < numToRead && filteredData.length < options.limit; i++) {
if (!response.items[i].confirmations) { //unconfirmed transactions
unconfirmedCount++;
if (numToRead < response.items[i].length)
numToRead++;
continue;
}
if (options.pattern) {
try {
let jsonContent = JSON.parse(response.items[i].floData);
if (!Object.keys(jsonContent).includes(options.pattern))
continue;
} catch (error) {
continue;
}
}
if (options.sentOnly) {
let flag = false;
for (let vin of response.items[i].vin)
if (vin.addr === addr) {
flag = true;
break;
}
if (!flag) continue;
}
if (Array.isArray(options.senders)) {
let flag = false;
for (let vin of response.items[i].vin)
if (options.senders.includes(vin.addr)) {
flag = true;
break;
}
if (!flag) continue;
}
if (options.receivedOnly) {
let flag = false;
for (let vout of response.items[i].vout)
if (vout.scriptPubKey.addresses[0] === addr) {
flag = true;
break;
}
if (!flag) continue;
}
if (Array.isArray(options.receivers)) {
let flag = false;
for (let vout of response.items[i].vout)
if (options.receivers.includes(vout.scriptPubKey.addresses[0])) {
flag = true;
break;
}
if (!flag) continue;
}
if (options.filter && !options.filter(response.items[i].floData))
continue;
if (options.tx) {
let d = {}
d.txid = response.items[i].txid;
d.time = response.items[i].time;
d.blockheight = response.items[i].blockheight;
d.senders = new Set(response.items[i].vin.map(v => v.addr));
d.receivers = new Set(response.items[i].vout.map(v => v.scriptPubKey.addresses[0]));
d.data = response.items[i].floData;
filteredData.push(d);
} else
filteredData.push(response.items[i].floData);
}
resolve({
totalTxs: response.totalItems - unconfirmedCount,
data: filteredData
});
}).catch(error => {
reject(error);
});
}).catch(error => {
reject(error);
});
});
}
})('object' === typeof module ? module.exports : window.floBlockchainAPI = {});

442
scripts/floCrypto.js Normal file
View File

@ -0,0 +1,442 @@
(function (EXPORTS) { //floCrypto v2.3.3e
/* FLO Crypto Operators */
'use strict';
const floCrypto = EXPORTS;
const p = BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16);
const ecparams = EllipticCurve.getSECCurveByName("secp256k1");
const ascii_alternatives = ` '\n '\n“ "\n” "\n --\n— ---\n≥ >=\n≤ <=\n≠ !=\n× *\n÷ /\n← <-\n→ ->\n↔ <->\n⇒ =>\n⇐ <=\n⇔ <=>`;
const exponent1 = () => p.add(BigInteger.ONE).divide(BigInteger("4"));
coinjs.compressed = true; //defaulting coinjs compressed to true;
function calculateY(x) {
let exp = exponent1();
// x is x value of public key in BigInteger format without 02 or 03 or 04 prefix
return x.modPow(BigInteger("3"), p).add(BigInteger("7")).mod(p).modPow(exp, p)
}
function getUncompressedPublicKey(compressedPublicKey) {
// Fetch x from compressedPublicKey
let pubKeyBytes = Crypto.util.hexToBytes(compressedPublicKey);
const prefix = pubKeyBytes.shift() // remove prefix
let prefix_modulus = prefix % 2;
pubKeyBytes.unshift(0) // add prefix 0
let x = new BigInteger(pubKeyBytes)
let xDecimalValue = x.toString()
// Fetch y
let y = calculateY(x);
let yDecimalValue = y.toString();
// verify y value
let resultBigInt = y.mod(BigInteger("2"));
let check = resultBigInt.toString() % 2;
if (prefix_modulus !== check)
yDecimalValue = y.negate().mod(p).toString();
return {
x: xDecimalValue,
y: yDecimalValue
};
}
function getSenderPublicKeyString() {
let privateKey = ellipticCurveEncryption.senderRandom();
var senderPublicKeyString = ellipticCurveEncryption.senderPublicString(privateKey);
return {
privateKey: privateKey,
senderPublicKeyString: senderPublicKeyString
}
}
function deriveSharedKeySender(receiverPublicKeyHex, senderPrivateKey) {
let receiverPublicKeyString = getUncompressedPublicKey(receiverPublicKeyHex);
var senderDerivedKey = ellipticCurveEncryption.senderSharedKeyDerivation(
receiverPublicKeyString.x, receiverPublicKeyString.y, senderPrivateKey);
return senderDerivedKey;
}
function deriveSharedKeyReceiver(senderPublicKeyString, receiverPrivateKey) {
return ellipticCurveEncryption.receiverSharedKeyDerivation(
senderPublicKeyString.XValuePublicString, senderPublicKeyString.YValuePublicString, receiverPrivateKey);
}
function getReceiverPublicKeyString(privateKey) {
return ellipticCurveEncryption.receiverPublicString(privateKey);
}
function wifToDecimal(pk_wif, isPubKeyCompressed = false) {
let pk = Bitcoin.Base58.decode(pk_wif)
pk.shift()
pk.splice(-4, 4)
//If the private key corresponded to a compressed public key, also drop the last byte (it should be 0x01).
if (isPubKeyCompressed == true) pk.pop()
pk.unshift(0)
let privateKeyDecimal = BigInteger(pk).toString()
let privateKeyHex = Crypto.util.bytesToHex(pk)
return {
privateKeyDecimal: privateKeyDecimal,
privateKeyHex: privateKeyHex
}
}
//generate a random Interger within range
floCrypto.randInt = function (min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(securedMathRandom() * (max - min + 1)) + min;
}
//generate a random String within length (options : alphaNumeric chars only)
floCrypto.randString = function (length, alphaNumeric = true) {
var result = '';
var characters = alphaNumeric ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' :
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():';
for (var i = 0; i < length; i++)
result += characters.charAt(Math.floor(securedMathRandom() * characters.length));
return result;
}
//Encrypt Data using public-key
floCrypto.encryptData = function (data, receiverPublicKeyHex) {
var senderECKeyData = getSenderPublicKeyString();
var senderDerivedKey = deriveSharedKeySender(receiverPublicKeyHex, senderECKeyData.privateKey);
let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue;
let secret = Crypto.AES.encrypt(data, senderKey);
return {
secret: secret,
senderPublicKeyString: senderECKeyData.senderPublicKeyString
};
}
//Decrypt Data using private-key
floCrypto.decryptData = function (data, privateKeyHex) {
var receiverECKeyData = {};
if (typeof privateKeyHex !== "string") throw new Error("No private key found.");
let privateKey = wifToDecimal(privateKeyHex, true);
if (typeof privateKey.privateKeyDecimal !== "string") throw new Error("Failed to detremine your private key.");
receiverECKeyData.privateKey = privateKey.privateKeyDecimal;
var receiverDerivedKey = deriveSharedKeyReceiver(data.senderPublicKeyString, receiverECKeyData.privateKey);
let receiverKey = receiverDerivedKey.XValue + receiverDerivedKey.YValue;
let decryptMsg = Crypto.AES.decrypt(data.secret, receiverKey);
return decryptMsg;
}
//Sign data using private-key
floCrypto.signData = function (data, privateKeyHex) {
var key = new Bitcoin.ECKey(privateKeyHex);
var messageHash = Crypto.SHA256(data);
var messageSign = Bitcoin.ECDSA.sign(messageHash, key.priv);
var sighex = Crypto.util.bytesToHex(messageSign);
return sighex;
}
//Verify signatue of the data using public-key
floCrypto.verifySign = function (data, signatureHex, publicKeyHex) {
var msgHash = Crypto.SHA256(data);
var sigBytes = Crypto.util.hexToBytes(signatureHex);
var publicKeyPoint = ecparams.getCurve().decodePointHex(publicKeyHex);
var verify = Bitcoin.ECDSA.verify(msgHash, sigBytes, publicKeyPoint);
return verify;
}
//Generates a new flo ID and returns private-key, public-key and floID
const generateNewID = floCrypto.generateNewID = function () {
var key = new Bitcoin.ECKey(false);
key.setCompressed(true);
return {
floID: key.getBitcoinAddress(),
pubKey: key.getPubKeyHex(),
privKey: key.getBitcoinWalletImportFormat()
}
}
Object.defineProperties(floCrypto, {
newID: {
get: () => generateNewID()
},
tmpID: {
get: () => {
let bytes = Crypto.util.randomBytes(20);
bytes.unshift(bitjs.pub);
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
asBytes: true
}), {
asBytes: true
});
var checksum = hash.slice(0, 4);
return bitjs.Base58.encode(bytes.concat(checksum));
}
}
});
//Returns public-key from private-key
floCrypto.getPubKeyHex = function (privateKeyHex) {
if (!privateKeyHex)
return null;
var key = new Bitcoin.ECKey(privateKeyHex);
if (key.priv == null)
return null;
key.setCompressed(true);
return key.getPubKeyHex();
}
//Returns flo-ID from public-key or private-key
floCrypto.getFloID = function (keyHex) {
if (!keyHex)
return null;
try {
var key = new Bitcoin.ECKey(keyHex);
if (key.priv == null)
key.setPub(keyHex);
return key.getBitcoinAddress();
} catch {
return null;
}
}
floCrypto.getAddress = function (privateKeyHex, strict = false) {
if (!privateKeyHex)
return;
var key = new Bitcoin.ECKey(privateKeyHex);
if (key.priv == null)
return null;
key.setCompressed(true);
let pubKey = key.getPubKeyHex(),
version = bitjs.Base58.decode(privateKeyHex)[0];
switch (version) {
case coinjs.priv: //BTC
return coinjs.bech32Address(pubKey).address;
case bitjs.priv: //FLO
return bitjs.pubkey2address(pubKey);
default:
return strict ? false : bitjs.pubkey2address(pubKey); //default to FLO address (if strict=false)
}
}
//Verify the private-key for the given public-key or flo-ID
floCrypto.verifyPrivKey = function (privateKeyHex, pubKey_floID, isfloID = true) {
if (!privateKeyHex || !pubKey_floID)
return false;
try {
var key = new Bitcoin.ECKey(privateKeyHex);
if (key.priv == null)
return false;
key.setCompressed(true);
if (isfloID && pubKey_floID == key.getBitcoinAddress())
return true;
else if (!isfloID && pubKey_floID == key.getPubKeyHex())
return true;
else
return false;
} catch {
return null;
}
}
//Check if the given flo-id is valid or not
floCrypto.validateFloID = function (floID) {
if (!floID)
return false;
try {
let addr = new Bitcoin.Address(floID);
return true;
} catch {
return false;
}
}
//Check if the given address (any blockchain) is valid or not
floCrypto.validateAddr = function (address, std = true, bech = true) {
let raw = decodeAddress(address);
if (!raw)
return false;
if (typeof raw.version !== 'undefined') { //legacy or segwit
if (std == false)
return false;
else if (std === true || (!Array.isArray(std) && std === raw.version) || (Array.isArray(std) && std.includes(raw.version)))
return true;
else
return false;
} else if (typeof raw.bech_version !== 'undefined') { //bech32
if (bech === false)
return false;
else if (bech === true || (!Array.isArray(bech) && bech === raw.bech_version) || (Array.isArray(bech) && bech.includes(raw.bech_version)))
return true;
else
return false;
} else //unknown
return false;
}
//Check the public-key for the address (any blockchain)
floCrypto.verifyPubKey = function (pubKeyHex, address) {
let raw = decodeAddress(address),
pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), {
asBytes: true
})));
return raw ? pub_hash === raw.hex : false;
}
//Convert the given address (any blockchain) to equivalent floID
floCrypto.toFloID = function (address, options = null) {
if (!address)
return;
let raw = decodeAddress(address);
if (!raw)
return;
else if (options) {
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
return;
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
return;
}
raw.bytes.unshift(bitjs.pub);
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
asBytes: true
}), {
asBytes: true
});
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
}
//Checks if the given addresses (any blockchain) are same (w.r.t keys)
floCrypto.isSameAddr = function (addr1, addr2) {
if (!addr1 || !addr2)
return;
let raw1 = decodeAddress(addr1),
raw2 = decodeAddress(addr2);
if (!raw1 || !raw2)
return false;
else
return raw1.hex === raw2.hex;
}
const decodeAddress = floCrypto.decodeAddr = function (address) {
if (!address)
return;
else if (address.length == 33 || address.length == 34) { //legacy encoding
let decode = bitjs.Base58.decode(address);
let bytes = decode.slice(0, decode.length - 4);
let checksum = decode.slice(decode.length - 4),
hash = Crypto.SHA256(Crypto.SHA256(bytes, {
asBytes: true
}), {
asBytes: true
});
return (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3]) ? null : {
version: bytes.shift(),
hex: Crypto.util.bytesToHex(bytes),
bytes
}
} else if (address.length == 42) { //bech encoding
let decode = coinjs.bech32_decode(address);
if (decode) {
let bytes = decode.data;
let bech_version = bytes.shift();
bytes = coinjs.bech32_convert(bytes, 5, 8, false);
return {
bech_version,
hrp: decode.hrp,
hex: Crypto.util.bytesToHex(bytes),
bytes
}
} else
return null;
}
}
//Split the str using shamir's Secret and Returns the shares
floCrypto.createShamirsSecretShares = function (str, total_shares, threshold_limit) {
try {
if (str.length > 0) {
var strHex = shamirSecretShare.str2hex(str);
var shares = shamirSecretShare.share(strHex, total_shares, threshold_limit);
return shares;
}
return false;
} catch {
return false
}
}
//Returns the retrived secret by combining the shamirs shares
const retrieveShamirSecret = floCrypto.retrieveShamirSecret = function (sharesArray) {
try {
if (sharesArray.length > 0) {
var comb = shamirSecretShare.combine(sharesArray.slice(0, sharesArray.length));
comb = shamirSecretShare.hex2str(comb);
return comb;
}
return false;
} catch {
return false;
}
}
//Verifies the shares and str
floCrypto.verifyShamirsSecret = function (sharesArray, str) {
if (!str)
return null;
else if (retrieveShamirSecret(sharesArray) === str)
return true;
else
return false;
}
const validateASCII = floCrypto.validateASCII = function (string, bool = true) {
if (typeof string !== "string")
return null;
if (bool) {
let x;
for (let i = 0; i < string.length; i++) {
x = string.charCodeAt(i);
if (x < 32 || x > 127)
return false;
}
return true;
} else {
let x, invalids = {};
for (let i = 0; i < string.length; i++) {
x = string.charCodeAt(i);
if (x < 32 || x > 127)
if (x in invalids)
invalids[string[i]].push(i)
else
invalids[string[i]] = [i];
}
if (Object.keys(invalids).length)
return invalids;
else
return true;
}
}
floCrypto.convertToASCII = function (string, mode = 'soft-remove') {
let chars = validateASCII(string, false);
if (chars === true)
return string;
else if (chars === null)
return null;
let convertor, result = string,
refAlt = {};
ascii_alternatives.split('\n').forEach(a => refAlt[a[0]] = a.slice(2));
mode = mode.toLowerCase();
if (mode === "hard-unicode")
convertor = (c) => `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
else if (mode === "soft-unicode")
convertor = (c) => refAlt[c] || `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
else if (mode === "hard-remove")
convertor = c => "";
else if (mode === "soft-remove")
convertor = c => refAlt[c] || "";
else
return null;
for (let c in chars)
result = result.replaceAll(c, convertor(c));
return result;
}
floCrypto.revertUnicode = function (string) {
return string.replace(/\\u[\dA-F]{4}/gi,
m => String.fromCharCode(parseInt(m.replace(/\\u/g, ''), 16)));
}
})('object' === typeof module ? module.exports : window.floCrypto = {});

1599
scripts/floExchangeAPI.js Normal file

File diff suppressed because it is too large Load Diff

9356
scripts/lib.js Normal file

File diff suppressed because it is too large Load Diff