Merge pull request #2 from ranchimall/dev

Dev
This commit is contained in:
sairaj mote 2022-12-05 01:14:38 +05:30 committed by GitHub
commit 61f9a03acc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 14903 additions and 11886 deletions

File diff suppressed because it is too large Load Diff

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -2,62 +2,77 @@
padding: 0; padding: 0;
margin: 0; margin: 0;
box-sizing: border-box; box-sizing: border-box;
font-family: 'Inter', sans-serif; font-family: "Roboto", sans-serif;
} }
:root { :root {
font-size: clamp(1rem, 1.2vmax, 3rem); font-size: clamp(16px, 1.2vmax, 32px);
} }
html, body{ html,
body {
height: 100%; height: 100%;
scroll-behavior: smooth; scroll-behavior: smooth;
} }
body { body {
--accent-color: #2353FF; --accent-color: #2353ff;
--light-shade: rgba(var(--text-color), 0.06); --text-color: 20, 20, 20;
--text-color: 17, 17, 17; --foreground-color: 252, 253, 255;
--text-color-light: 100, 100, 100; --background-color: 241, 243, 248;
--foreground-color: 255, 255, 255; --danger-color: rgb(255, 75, 75);
--background-color: #F6f6f6; --green: #1cad59;
--error-color: red; --yellow: rgb(220, 165, 0);
--green: #00843b;
color: rgba(var(--text-color), 1); color: rgba(var(--text-color), 1);
background: var(--background-color); background: rgba(var(--background-color), 1);
} }
body[data-theme='dark']{ body[data-theme="dark"] {
--accent-color: #2353FF; --accent-color: #7e9aff;
--green: #13ff5a; --text-color: 200, 200, 200;
--text-color: 240, 240, 240; --foreground-color: 27, 28, 29;
--text-color-light: 170, 170, 170; --background-color: 21, 22, 22;
--foreground-color: 20, 20, 20; --danger-color: rgb(255, 106, 106);
--background-color: #0a0a0a; --green: #00e676;
--error-color: rgb(255, 106, 106); --yellow: rgb(255, 213, 5);
::-webkit-calendar-picker-indicator { ::-webkit-calendar-picker-indicator {
filter: invert(1); filter: invert(1);
} }
sm-input.outlined{
--active-placeholder-color: rgba(var(--text-color), 1);
} }
.overpass {
font-family: "Overpass", sans-serif;
font-weight: 700;
} }
h1,
.full-bleed{ h2,
grid-column: 1/4 !important; h3,
h4,
h5,
h6 {
font-family: "Overpass", sans-serif;
font-weight: 700;
} }
.h1 { .h1 {
font-size: 2.5rem; font-size: 6rem;
} }
.h2 { .h2 {
font-size: 2rem; font-size: 4rem;
} }
.h3 { .h3 {
font-size: 1.4rem; font-size: 3rem;
line-height: 1.1;
} }
.h4 { .h4 {
font-size: 1rem; font-size: 2rem;
} }
.h5 { .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 { .uppercase {
text-transform: uppercase; text-transform: uppercase;
} }
@ -68,7 +83,7 @@ body[data-theme='dark']{
p { p {
font-size: 0.8; font-size: 0.8;
max-width: 70ch; max-width: 70ch;
line-height: 1.5; line-height: 1.7;
color: rgba(var(--text-color), 0.8); color: rgba(var(--text-color), 0.8);
} }
@ -77,38 +92,88 @@ img{
} }
a { a {
color: var(--accent-color);
}
a:any-link:focus-visible {
outline: var(--accent-color) medium solid;
}
button,
.button {
user-select: none;
position: relative;
display: inline-flex;
border: none;
background-color: transparent;
overflow: hidden;
color: inherit; color: inherit;
text-decoration: none; -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;
justify-content: center;
&:focus-visible { &:focus-visible {
box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1) inset; outline: var(--accent-color) solid medium;
}
&:not(:disabled) {
cursor: pointer;
} }
} }
button{ .button {
position: relative; background-color: rgba(var(--text-color), 0.02);
display: inline-flex; border: solid thin rgba(var(--text-color), 0.06);
overflow: hidden; &--primary {
align-items: center; color: rgba(var(--background-color), 1) !important;
background: none;
cursor: pointer;
outline: none;
color: inherit;
font-size: 0.9rem;
font-weight: 500;
border-radius: 0.2rem;
padding: 0.5rem 0.6rem;
-webkit-tap-highlight-color: transparent;
border: none;
}
.button--primary{
background: var(--accent-color);
color: white;
.icon { .icon {
fill: rgba(var(--foreground-color), 1); fill: rgba(var(--background-color), 1);
} }
} }
button:focus-visible{ &--danger {
outline: rgba(var(--text-color), 1) 0.1rem solid; color: var(--danger-color);
.icon {
fill: var(--danger-color);
}
}
&--primary {
background-color: var(--accent-color);
}
&--colored {
color: var(--accent-color);
.icon {
fill: var(--accent-color);
}
}
&--small {
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 {
cursor: not-allowed;
filter: saturate(0);
}
.cta {
text-transform: uppercase;
font-weight: 700;
letter-spacing: 0.05em;
padding: 0.8rem 1rem;
} }
a:any-link { a:any-link {
position: relative; position: relative;
@ -117,7 +182,7 @@ a:any-link{
background: none; background: none;
cursor: pointer; cursor: pointer;
outline: none; outline: none;
color: inherit; color: var(--accent-color);
font-weight: 500; font-weight: 500;
font-size: 0.8rem; font-size: 0.8rem;
border-radius: 0.3rem; border-radius: 0.3rem;
@ -125,30 +190,54 @@ a:any-link{
align-self: flex-start; align-self: flex-start;
text-decoration: none; text-decoration: none;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
border: 1px solid rgba(var(--text-color), 0.8); border: 1px solid var(--accent-color);
} }
a:any-link:focus-visible { a:any-link:focus-visible {
outline: rgba(var(--text-color), 1) 0.1rem solid; outline: rgba(var(--text-color), 1) 0.1rem solid;
} }
.icon {
height: 1.2rem;
width: 1.2rem;
fill: rgba(var(--text-color), 1);
}
.multi-state-button {
display: grid;
text-align: center;
align-items: center;
& > * {
grid-area: 1/1/2/2;
}
button {
z-index: 1;
}
sm-spinner {
justify-self: center;
}
}
sm-spinner {
--size: 1.5rem;
--stroke-width: 0.1rem;
}
.password-field {
label {
display: flex;
justify-content: center;
input:checked ~ .visible {
display: none;
}
input:not(:checked) ~ .invisible {
display: none;
}
}
}
sm-input, sm-input,
sm-textarea { sm-textarea {
--border-radius: 0.2rem; --border-radius: 0.5rem;
--background: rgba(var(--text-color), 0.06); --background: rgba(var(--text-color), 0.06);
} }
sm-button{
--border-radius: 0.2rem;
}
sm-tab-header{
align-self: flex-start;
--active-tab-color: white;
margin-bottom: 1rem;
justify-self: flex-start;
padding: 0.3rem;
border-radius: 0.5rem;
background-color: rgba(var(--text-color), 0.06);
}
ul { ul {
list-style: none; list-style: none;
} }
@ -158,6 +247,9 @@ ul{
.flex { .flex {
display: flex; display: flex;
} }
.flex-wrap {
flex-wrap: wrap;
}
.grid { .grid {
display: grid; display: grid;
} }
@ -191,7 +283,7 @@ ul{
.align-start { .align-start {
align-items: flex-start; align-items: flex-start;
} }
.align-center{ .align-items-center {
align-items: center; align-items: center;
} }
.align-end { .align-end {
@ -286,12 +378,9 @@ ul{
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
} }
.hide-completely{ .hidden {
display: none !important; display: none !important;
} }
.no-transformations{
transform: none !important;
}
.overflow-ellipsis { .overflow-ellipsis {
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
@ -309,10 +398,16 @@ ul{
hyphens: auto; hyphens: auto;
} }
.ripple { .ripple {
height: 8rem;
width: 8rem;
position: absolute; position: absolute;
border-radius: 50%; border-radius: 50%;
transform: scale(0); 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; pointer-events: none;
} }
.interact { .interact {
@ -327,10 +422,26 @@ ul{
.observe-empty-state:not(:empty) ~ .empty-state { .observe-empty-state:not(:empty) ~ .empty-state {
display: none; display: none;
} }
.icon{ .popup__header {
width: 1.5rem; position: relative;
height: 1.5rem; display: grid;
fill: rgba(var(--text-color), 0.9); gap: 0.5rem;
width: 100%;
padding: 0 1.5rem 0 0.5rem;
align-items: center;
grid-template-columns: auto 1fr;
& > * {
grid-row: 1;
}
h3,
h4 {
grid-column: 1/-1;
justify-self: center;
align-self: center;
}
&__close {
grid-column: 1;
}
} }
#loading_page, #loading_page,
@ -436,29 +547,24 @@ ul{
} }
} }
#main_header { #main_header {
position: relative; position: relative;
display: grid; display: grid;
gap: 1.5rem; gap: 1.5rem;
padding: 1rem; padding: 1rem;
align-items: center; align-items: center;
background-color: var(--accent-color); background-color: #2353ff;
color: white; color: white;
} }
#main_header__logo { #main_header__logo {
fill: white; fill: white;
height: 2.4rem; height: 1.5rem;
width: 2.4rem; width: 1.5rem;
margin-left: -0.3rem;
} }
.header__company-name { .header__company-name {
font-size: 1em;
font-weight: 500; font-weight: 500;
} }
.header__app-name{
font-weight: 700;
font-size: 1.2rem;
}
#current_price { #current_price {
justify-self: flex-start; justify-self: flex-start;
} }
@ -466,7 +572,7 @@ ul{
display: flex; display: flex;
align-items: center; align-items: center;
&::before { &::before {
content: ''; content: "";
width: 1px; width: 1px;
height: 2rem; height: 2rem;
background-color: white; background-color: white;
@ -474,14 +580,6 @@ ul{
} }
} }
sm-select{
min-width: 6rem;
--selection-font-size: 0.9rem;
}
sm-option{
--color: rgba(var(--text-color), 1);
}
.dropdown { .dropdown {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
@ -511,77 +609,77 @@ sm-option{
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border: none; border: none;
height: 2.2rem;
width: 2.2rem;
justify-self: flex-end; justify-self: flex-end;
border-radius: 50%; border-radius: 50%;
padding: 0.5rem;
background-color: rgba($color: #000000, $alpha: 0.16); background-color: rgba($color: #000000, $alpha: 0.16);
.icon { .icon {
height: 1rem;
width: 1rem;
fill: white; fill: white;
} }
} }
#theme_switcher{
overflow: hidden;
width: 100%;
}
.page-layout { .page-layout {
display: grid; display: grid;
grid-template-columns: 1rem 1fr 1rem; grid-template-columns: 1rem minmax(0, 1fr) 1rem;
& > * { & > * {
grid-column: 2/3; grid-column: 2/3;
} }
} }
#home_page { #home_page {
--side-padding: 1rem;
display: flex;
flex-direction: column;
padding: 0 max(1rem, var(--side-padding));
padding-bottom: 6rem; padding-bottom: 6rem;
} }
#homepage__hero-section { #homepage__hero-section {
color: white; color: white;
padding: 2rem 0 12rem 0; padding: 2rem var(--side-padding) 6rem var(--side-padding);
margin-bottom: -8rem; margin: 0 calc(-1 * max(1rem, var(--side-padding))) -1.8rem calc(-1 * max(1rem, var(--side-padding)));
background-image: url(../assets/bg-1.svg); background-image: url(../assets/bg-1.svg);
background-color: var(--accent-color); background-color: #2353ff;
background-size: cover; background-size: cover;
p { p {
color: rgba($color: #fff, $alpha: 0.8); color: rgba($color: #fff, $alpha: 0.8);
} }
} }
#refresh_button {
color: var(--accent-color);
border: solid thin rgba(0, 0, 0, 0.2);
}
.fund-list__header { .fund-list__header {
color: white; display: flex;
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
margin-bottom: 1rem;
} }
#search_investor { #search_investor {
--font-weight: 500; position: sticky;
--padding: 0.5rem 1rem; top: 1rem;
--background: rgba(var(--foreground-color), 1); --background: rgba(var(--foreground-color), 1);
border: solid thin rgba(var(--text-color), 0.2);
color: rgba(var(--text-color), 1); color: rgba(var(--text-color), 1);
grid-area: search; --padding: 1rem;
width: min(24rem ,100%); width: min(26rem, 100%);
justify-self: flex-end; align-self: center;
.icon{ margin-bottom: 1.5rem;
height: 1.2rem; border-radius: 0.5rem;
width: 1.2rem; box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
margin-left: -0.5rem; z-index: 1;
}
} }
.label { .label {
font-weight: 500; font-weight: 500;
font-size: 0.75rem; font-size: 0.85rem;
margin-bottom: 0.2rem; margin-bottom: 0.3rem;
color: rgba(var(--text-color), 0.8); color: rgba(var(--text-color), 0.8);
} }
.value { .value {
font-weight: 600; font-weight: 700;
font-size: 0.9rem; font-size: 1rem;
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; word-wrap: break-word;
-ms-word-break: break-all; -ms-word-break: break-all;
@ -590,6 +688,7 @@ sm-option{
-moz-hyphens: auto; -moz-hyphens: auto;
-webkit-hyphens: auto; -webkit-hyphens: auto;
hyphens: auto; hyphens: auto;
min-width: 6rem;
} }
.warning-container { .warning-container {
@ -601,8 +700,9 @@ sm-option{
} }
#fund_list { #fund_list {
display: grid; display: flex;
gap: 1.5rem; flex-direction: column;
gap: 1rem;
} }
#fund_list__empty-state { #fund_list__empty-state {
@ -618,37 +718,6 @@ sm-option{
justify-self: center; justify-self: center;
} }
} }
fund-block{
&:first-of-type::before{
display: inline-flex;
content: '• Latest Fund';
padding: 0.4rem 0.8rem;
font-weight: 700;
font-size: 1rem;
color: white;
background-color: var(--accent-color);
border-radius: 0.3rem 0.3rem 0 0;
}
&:not(:last-of-type)::after{
display: flex;
content: '';
// justify-content: center;
padding: 0.4rem 0;
font-weight: 700;
width: 100%;
// font-size: 0.9rem;
// color: var(--background-color);
background-color: var(--error-color);
border-radius: 0 0 0.5rem 0.5rem;
}
}
.flex-grid{
grid-template-columns: repeat(auto-fill, minmax(24rem, 1fr));
}
form { form {
padding: 1rem; padding: 1rem;
border-radius: 0.3rem; border-radius: 0.3rem;
@ -683,89 +752,151 @@ form{
line-height: 1.7; line-height: 1.7;
margin-bottom: 2rem; margin-bottom: 2rem;
} }
.investors-list {
.flex-grid{
display: grid; display: grid;
gap: 1rem; gap: 1rem;
justify-content: start;
grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
} }
.fund-placeholder{ .fund-investor {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
gap: 1rem; gap: 1rem;
padding: 1rem; padding: 1rem;
border-radius: 0.3rem; border-radius: 0.5rem;
border: solid thin rgba(var(--text-color), 0.2);
& > .grid:first-of-type {
width: min(40ch, 100%);
}
&__withdraw {
background-color: var(--accent-color);
border-radius: 3rem;
padding: 0.8rem 1.5rem;
color: rgba(var(--background-color), 1);
align-self: flex-start;
}
.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);
}
}
.transaction-column {
display: flex;
flex-direction: column;
}
.fund-block {
border-radius: 0.5rem;
padding: 1rem;
background-color: rgba(var(--foreground-color), 1); background-color: rgba(var(--foreground-color), 1);
box-shadow: 0 1rem 2rem -1rem rgba($color: #000000, $alpha: 0.16); box-shadow: 0 1rem 2rem -1rem rgba($color: #000000, $alpha: 0.16);
.placeholder__block:first-of-type{
width: min(24rem, 100%);
} }
&:nth-of-type(2) .placeholder__block{ .fund-block__details {
animation-delay: 0.3s;
}
&:nth-of-type(3) .placeholder__block{
animation-delay: 0.6s;
}
&:nth-of-type(4) .placeholder__block{
animation-delay: 0.8s;
}
}
.placeholder__block{
display: flex; display: flex;
border-radius: 0.3rem; flex-wrap: wrap;
min-width: 9rem; gap: 2rem 3rem;
padding: 1.2rem 1rem;
animation: pulse alternate 0.6s ease infinite;
background-color: rgba(var(--text-color), 0.1);
} }
@keyframes pulse{ .start-date {
from{ display: flex;
opacity: 0.4; align-items: center;
margin-bottom: 2rem;
background-color: rgba(var(--text-color), 0.06);
padding: 0.8rem 1rem;
border-radius: 0.5rem;
width: max-content;
&::before {
content: "";
display: inline-block;
width: 0.5em;
height: 0.5em;
margin-right: 0.5rem;
border-radius: 50%;
background-color: var(--accent-color);
} }
to{ }
.tapout-list {
display: flex;
flex-wrap: wrap;
gap: 1rem 1.5rem;
justify-content: flex-start;
}
.tapout-list li {
min-width: 8rem;
}
.fund-link {
justify-self: flex-end;
}
.user-action-result__icon {
justify-self: center;
height: 4rem;
width: 4rem;
border-radius: 5rem;
margin-bottom: 2rem;
animation: popup 1s;
&.success {
fill: rgba(var(--background-color), 1);
padding: 1rem;
background-color: #0bbe56;
}
&.failed {
background-color: rgba(var(--text-color), 0.03);
fill: var(--danger-color);
}
}
@keyframes popup {
0% {
opacity: 0;
transform: scale(0.2) translateY(600%);
}
10% {
transform: scale(0.2) translateY(5rem);
opacity: 1; 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) { @media only screen and (max-width: 640px) {
.h1{
font-size: 2rem;
}
.h2{
font-size: 1.8rem;
}
.h3{
font-size: 1.3rem;
}
.h4{
font-size: 1rem;
}
.h5{
font-size: 0.8rem;
}
#main_header { #main_header {
grid-template-areas: '. profile-button'; grid-template-areas: ". profile-button";
.dropdown { .dropdown {
grid-area: profile-button; grid-area: profile-button;
} }
} }
#homepage__hero-section{
padding: 2rem 0 20rem 0;
margin-bottom: -16rem;
}
.fund-list__header{
grid-template-columns: auto 1fr;
grid-template-areas: '. .' 'search search';
}
.investor-input { .investor-input {
grid-template-columns: 1fr auto; grid-template-columns: 1fr auto;
grid-template-areas: '. close' '. close'; grid-template-areas: ". close" ". close";
}
.fund-block {
margin: 0 -1rem;
}
.fund-link {
grid-column: 2/3;
} }
} }
@media only screen and (min-width: 640px) { @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 { #main_header {
padding: 2rem calc((10vw / 2) - 0.4rem); padding: 2rem calc((10vw / 2) - 0.4rem);
grid-template-columns: auto 1fr auto; grid-template-columns: auto 1fr auto;
@ -776,19 +907,28 @@ form{
.page-layout { .page-layout {
grid-template-columns: 1fr 90vw 1fr; grid-template-columns: 1fr 90vw 1fr;
} }
.value {
min-width: 9rem;
}
#current_price { #current_price {
justify-self: flex-end; justify-self: flex-end;
} }
.dropdown__panel { .dropdown__panel {
left: auto; left: auto;
} }
.fund-list__header{
grid-template-columns: auto 1fr auto;
grid-template-areas: '. search .';
}
.investor-input { .investor-input {
grid-template-columns: 1.5fr 1fr auto; grid-template-columns: 1.5fr 1fr auto;
grid-template-areas: '. . close'; grid-template-areas: ". . close";
}
.fund-block {
padding: 3vw;
}
.fund-investor {
padding: 1.5rem;
}
.justify-self-end {
justify-self: flex-end;
text-align: right;
} }
} }
@media only screen and (min-width: 1280px) { @media only screen and (min-width: 1280px) {

11450
index.html

File diff suppressed because it is too large Load Diff

262
scripts/bobs-fund.js Normal file
View File

@ -0,0 +1,262 @@
const bobsFund = (function () {
const productStr = "Bobs Fund";
const magnitude = m => {
switch (m) {
case "thousand": return 1000;
case "lakh": case "lakhs": return 100000;
case "million": return 1000000;
case "crore": case "crores": return 10000000;
default: return null;
}
}
const parseNumber = (str) => {
let n = 0,
g = 0;
str.toLowerCase().replace(/,/g, '').split(" ").forEach(s => {
if (!isNaN(s))
g = parseFloat(s);
else {
let m = magnitude(s);
if (m !== null) {
n += m * g;
g = 0;
}
}
});
return n + g;
}
const parsePeriod = (str) => {
let P = '', n = 0;
str.toLowerCase().replace(/,/g, '').split(" ").forEach(s => {
if (!isNaN(s))
n = parseFloat(s);
else switch (s) {
case "year(s)": case "year": case "years": P += (n + 'Y'); n = 0; break;
case "month(s)": case "month": case "months": P += (n + 'M'); n = 0; break;
case "day(s)": case "day": case "days": P += (n + 'D'); n = 0; break;
}
});
return P;
}
const dateFormat = (date = null) => {
let d = (date ? new Date(date) : new Date()).toDateString();
return [d.substring(8, 10), d.substring(4, 7), d.substring(11, 15)].join(" ");
}
const dateAdder = function (start_date, duration) {
let date = new Date(start_date);
let y = parseInt(duration.match(/\d+Y/)),
m = parseInt(duration.match(/\d+M/)),
d = parseInt(duration.match(/\d+D/));
if (!isNaN(y))
date.setFullYear(date.getFullYear() + y);
if (!isNaN(m))
date.setMonth(date.getMonth() + m);
if (!isNaN(d))
date.setDate(date.getDate() + d);
return date;
}
function calcNetValue(BTC_base, BTC_net, USD_base, USD_net, amount, fee) {
let gain, interest, net;
gain = (BTC_net - BTC_base) / BTC_base;
interest = gain * (1 - fee)
net = amount / USD_base;
net += net * interest;
return net * USD_net;
}
function stringify_main(BTC_base, USD_base, start_date, duration, investments, fee = 0, tapoutWindow = null, tapoutInterval = null) {
let result = [
`${productStr}`,
`Base Value: ${BTC_base} USD`,
`USD INR rate at start: ${USD_base}`,
`Start date: ${dateFormat(start_date)}`,
`Duration: ${duration}`,
`Management Fee: ${fee != 0 ? fee + "%" : "0 (Zero)"}`
];
if (tapoutInterval) {
if (Array.isArray(tapoutInterval)) {
let x = tapoutInterval.pop(),
y = tapoutInterval.join(", ")
tapoutInterval = `${y} and ${x}`
}
result.push(`Tapout availability: ${tapoutWindow} after ${tapoutInterval}`);
}
result.push(`Investment(s) (INR): ${investments.map(f => `${f[0].trim()}-${f[1].trim()}`).join("; ")}`);
return result.join("|");
}
function stringify_continue(fund_id, investments) {
return [
`${productStr}`,
`continue: ${fund_id}`,
`Investment(s) (INR): ${investments.map(f => `${f[0].trim()}-${f[1].trim()}`).join("; ")}`
].join("|");
}
function stringify_end(fund_id, floID, end_date, BTC_net, USD_net, amount, ref_sign, payment_ref) {
return [
`${productStr}`,
`close: ${fund_id}`,
`Investor: ${floID}`,
`End value: ${BTC_net} USD`,
`Date of withdrawal: ${dateFormat(end_date)}`,
`USD INR rate at end: ${USD_net}`,
`Amount withdrawn: Rs ${amount} via ${payment_ref}`,
`Reference: ${ref_sign}`
].join("|");
}
function parse_details(data) {
let funds = {};
funds.investments = {};
if (!Array.isArray(data))
data = [data];
data.forEach((fd, i) => {
if (!/close: [a-z0-9]{64}\|/.test(fd)) { // not a closing tx
let cont = /continue: [a-z0-9]{64}\|/.test(fd);
fd.split("|").forEach(d => {
d = d.split(': ');
if (["invesment(s) (inr)", "investment(s) (inr)"].includes(d[0].toLowerCase()))
d[1].split(";").forEach(a => {
a = a.split("-");
let floID = a[0].replace(/\s/g, ''); //for removing spaces (trailing) if any
funds["investments"][floID] = funds["investments"][floID] || {};
funds["investments"][floID].amount = parseNumber(a[1]);
funds["investments"][floID].i = i;
});
else if (!cont)
switch (d[0].toLowerCase()) {
case "start date":
funds["start_date"] = new Date(d[1]); break;
case "base value":
funds["BTC_base"] = parseNumber(d[1].slice(0, -4)); break;
case "usd inr rate at start":
funds["USD_base"] = parseFloat(d[1]); break;
case "duration":
funds["duration"] = parsePeriod(d[1]); break;
case "management fee":
funds["fee"] = parseFloat(d[1]); break;
case "tapout availability":
let x = d[1].toLowerCase().split("after")
funds["tapoutInterval"] = x[1].match(/\d+ [a-z]+/gi).map(y => parsePeriod(y))
funds["topoutWindow"] = parsePeriod(x[0]); break;
}
});
} else {
let floID, details = {};
fd.split("|").forEach(d => {
d = d.split(': ');
switch (d[0].toLowerCase()) {
case "investor":
floID = d[1]; break;
case "end value":
details["BTC_net"] = parseNumber(d[1].slice(0, -4)); break;
case "date of withdrawal":
details["endDate"] = new Date(d[1]); break;
case "amount withdrawn":
details["amountFinal"] = parseNumber(d[1].match(/\d.+ via/).toString());
details["payment_refRef"] = d[1].match(/via .+/).toString().substring(4); break;
case "usd inr rate at end":
details["USD_net"] = parseFloat(d[1]); break;
case "reference":
details["refSign"] = d[1]; break;
}
});
if (floID) {
funds.investments[floID] = funds.investments[floID] || {};
funds.investments[floID].closed = details;
funds.investments[floID].closed.i = i;
}
}
});
return funds;
}
return {
productStr,
dateAdder,
dateFormat,
calcNetValue,
parse: parse_details,
stringify: {
main: stringify_main,
continue: stringify_continue,
end: stringify_end
}
}
})();
function refreshBlockchainData(newOnly = false) {
return new Promise((resolve, reject) => {
compactIDB.readData("appendix", "lastTx").then(lastTx => {
floBlockchainAPI.readData(floGlobals.adminID, {
ignoreOld: lastTx,
senders: floExchangeAPI.nodeList.concat(floGlobals.adminID), //sentOnly: true,
tx: true,
filter: d => d.startsWith(bobsFund.productStr)
}).then(result => {
compactIDB.readAllData("funds").then(funds => {
let writeKeys = new Set();
result.data.reverse().forEach(d => {
if (/close: /i.test(d.data)) {
let ctx = d.data.match(/close: [0-9a-z]{64}/i).toString().split(": ")[1];
funds[ctx].push({
txid: d.txid,
data: d.data
})
writeKeys.add(ctx);
} else if (d.senders.has(floGlobals.adminID)) {
if (/continue: /i.test(d.data)) {
let ctx = d.data.match(/continue: [0-9a-z]{64}/i).toString().split(": ")[1];
funds[ctx].push({
txid: d.txid,
data: d.data
})
writeKeys.add(ctx);
} else if (/start: /i.test(d.data)) {
funds[d.txid] = [{
txid: d.txid,
data: d.data
}]
writeKeys.add(d.txid);
}
}
})
writeKeys = Array.from(writeKeys);
Promise.all(writeKeys.map(k => compactIDB.writeData("funds", funds[k], k))).then(results => {
compactIDB.writeData('appendix', result.totalTxs, "lastTx");
resolve(newOnly ? writeKeys.map(k => funds[k]) : funds)
}).catch(error => reject(error))
}).catch(error => reject(error))
}).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