Integrating smart contract system

This commit is contained in:
sairaj mote 2023-04-24 01:39:52 +05:30
parent ec718a3c4e
commit cbf9ad21a9
5 changed files with 1204 additions and 113 deletions

View File

@ -69,6 +69,9 @@ body[data-theme=dark] {
body[data-theme=dark] sm-popup::part(popup) { body[data-theme=dark] sm-popup::part(popup) {
background-color: rgba(var(--foreground-color), 1); background-color: rgba(var(--foreground-color), 1);
} }
body[data-theme=dark] ::-webkit-calendar-picker-indicator {
filter: invert(1);
}
p, p,
strong { strong {
@ -251,6 +254,17 @@ sm-chip[selected] {
color: rgba(var(--background-color), 1); color: rgba(var(--background-color), 1);
} }
sm-select::part(options) {
max-height: 40vh;
}
sm-option {
flex-shrink: 0;
}
sm-option::part(option) {
grid-template-columns: none;
}
ul { ul {
list-style: none; list-style: none;
} }
@ -266,8 +280,6 @@ ul {
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; word-wrap: break-word;
word-break: break-word; word-break: break-word;
-webkit-hyphens: auto;
hyphens: auto;
} }
.full-bleed { .full-bleed {
@ -316,6 +328,10 @@ h3 {
display: flex; display: flex;
} }
.flex-1 {
flex: 1;
}
.flex-wrap { .flex-wrap {
flex-wrap: wrap; flex-wrap: wrap;
} }
@ -368,6 +384,10 @@ h3 {
align-items: flex-end; align-items: flex-end;
} }
.align-content-center {
align-content: center;
}
.text-center { .text-center {
text-align: center; text-align: center;
} }
@ -384,6 +404,10 @@ h3 {
margin-left: auto; margin-left: auto;
} }
.justify-items-center {
justify-items: center;
}
.align-self-center { .align-self-center {
align-self: center; align-self: center;
} }
@ -416,6 +440,22 @@ h3 {
height: 100%; height: 100%;
} }
.margin-left-0-5 {
margin-left: 0.5rem;
}
.margin-left-auto {
margin-left: auto;
}
.margin-right-0-5 {
margin-right: 0.5rem;
}
.margin-right-auto {
margin-right: auto;
}
.ripple { .ripple {
height: 8rem; height: 8rem;
width: 8rem; width: 8rem;
@ -473,18 +513,6 @@ h3 {
flex-shrink: 0; flex-shrink: 0;
} }
.margin-right-0-5 {
margin-right: 0.5rem;
}
.margin-left-0-5 {
margin-left: 0.5rem;
}
.margin-left-auto {
margin-left: auto;
}
.icon-button { .icon-button {
padding: 0.6rem; padding: 0.6rem;
border-radius: 0.8rem; border-radius: 0.8rem;
@ -533,6 +561,21 @@ h3 {
justify-self: flex-start; justify-self: flex-start;
} }
#loader {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(var(--foreground-color), 1);
z-index: 100;
display: grid;
place-content: center;
place-items: center;
gap: 1rem;
text-align: center;
}
#show_character_count { #show_character_count {
font-size: 0.8rem; font-size: 0.8rem;
margin-left: auto; margin-left: auto;
@ -548,7 +591,7 @@ h3 {
} }
#main_header { #main_header {
padding: 1rem 1.5rem; padding: 0.6rem 1rem;
} }
#main_card { #main_card {
@ -586,6 +629,7 @@ h3 {
display: flex; display: flex;
flex: 1; flex: 1;
width: 100%; width: 100%;
height: 100%;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -593,12 +637,12 @@ h3 {
color: var(--text-color); color: var(--text-color);
font-size: 0.7rem; font-size: 0.7rem;
border-radius: 0.3rem; border-radius: 0.3rem;
text-align: center;
} }
.nav-item .icon { .nav-item .icon {
transition: transform 0.2s; transition: transform 0.2s;
} }
.nav-item__title { .nav-item__title {
margin-top: 0.3rem;
transition: opacity 0.2s, transform 0.2s; transition: opacity 0.2s, transform 0.2s;
} }
.nav-item--active { .nav-item--active {
@ -606,11 +650,6 @@ h3 {
} }
.nav-item--active .icon { .nav-item--active .icon {
fill: var(--accent-color); fill: var(--accent-color);
transform: translateY(50%);
}
.nav-item--active .nav-item__title {
transform: translateY(100%);
opacity: 0;
} }
.nav-item__indicator { .nav-item__indicator {
position: absolute; position: absolute;
@ -977,6 +1016,117 @@ h3 {
transform: scale(1) translateY(0); transform: scale(1) translateY(0);
} }
} }
#smartcontracts {
display: grid;
min-width: 0;
height: 100%;
}
#smartcontracts > * {
grid-area: 1/1;
}
#smartcontracts fieldset {
padding: 0.5rem;
border-radius: 0.5rem;
border: solid 1px rgba(var(--text-color), 0.3);
}
#smartcontracts fieldset legend {
padding: 0 0.5rem;
}
#smartcontracts legend,
#smartcontracts .label {
font-size: 0.8rem;
color: rgba(var(--text-color), 0.8);
}
#smartcontracts label {
padding: 0.3rem 0.5rem;
}
#smartcontracts label:has(input:not(:disabled):not(:checked)) {
cursor: pointer;
}
#smartcontracts input[type=radio] {
height: 1.1em;
width: 1.1em;
margin-right: 0.5rem;
accent-color: var(--accent-color);
}
#smartcontracts input[type=datetime-local] {
width: 100%;
padding: 0.8rem 0.6rem;
border: none;
border-radius: 0.5rem;
font-weight: 500;
font-family: inherit;
font-size: inherit;
color: inherit;
background-color: rgba(var(--text-color), 0.06);
}
#smartcontracts input[type=datetime-local]:focus {
outline: none;
box-shadow: 0 0 0 0.1rem var(--accent-color);
}
#smartcontracts sm-input:not([placeholder]) {
--min-height: 3rem;
}
.smart-contract-action {
flex-direction: column;
white-space: normal;
gap: 0.5rem;
color: rgba(var(--text-color), 0.8);
width: 5rem;
font-size: 0.8rem;
aspect-ratio: 1/1;
border-radius: 0.5rem;
border: solid 1px rgba(var(--text-color), 0.3);
}
.smart-contract-action .icon {
fill: var(--accent-color);
}
#smart_contract_creation_templates {
display: grid;
gap: 0.5rem;
}
#smart_contract_creation_templates li {
display: flex;
flex: 1;
}
.smart-contract-template {
display: grid;
grid-template-areas: "heading arrow" "description arrow";
grid-template-columns: 1fr auto;
padding: max(1rem, 2vw);
border: solid 1px rgba(var(--text-color), 0.3);
border-radius: 0.5rem;
gap: 0.5rem;
text-align: start;
white-space: normal;
width: 100%;
justify-content: flex-start;
}
.smart-contract-template h4 {
grid-area: heading;
}
.smart-contract-template p {
grid-area: description;
font-weight: 400;
line-height: 1.3;
font-size: 0.9rem;
color: rgba(var(--text-color), 0.8);
text-transform: none;
}
.smart-contract-template .icon {
margin-top: auto;
grid-area: arrow;
border: solid 1px rgba(var(--text-color), 0.3);
border-radius: 3rem;
padding: 0.4rem;
height: 2rem;
width: 2rem;
fill: var(--accent-color);
}
@media screen and (max-width: 40rem) { @media screen and (max-width: 40rem) {
#main_navbar.hide-away { #main_navbar.hide-away {
bottom: 0; bottom: 0;
@ -986,11 +1136,18 @@ h3 {
#primary_actions_wrapper { #primary_actions_wrapper {
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
} }
.nav-item__title {
margin-top: 0.3rem;
}
.nav-item--active .icon {
transform: translateY(50%);
}
.nav-item--active .nav-item__title {
transform: translateY(100%);
opacity: 0;
}
} }
@media screen and (min-width: 40rem) { @media screen and (min-width: 40rem) {
body {
background-size: cover;
}
sm-popup { sm-popup {
--width: 24rem; --width: 24rem;
} }
@ -1006,11 +1163,8 @@ h3 {
display: grid; display: grid;
grid-template-columns: auto 1fr; grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
grid-template-areas: "nav header" "nav main"; grid-template-areas: "header header" "nav main";
position: relative; position: relative;
overflow: hidden;
box-shadow: 0 0.1rem 0.2rem rgba(0, 0, 0, 0.05), 0 1rem 3rem rgba(0, 0, 0, 0.2);
background-color: rgba(var(--foreground-color), 0.9);
} }
#main_header { #main_header {
grid-area: header; grid-area: header;
@ -1026,14 +1180,16 @@ h3 {
} }
#main_navbar ul { #main_navbar ul {
flex-direction: column; flex-direction: column;
gap: 0.5rem;
padding: 0.3rem;
}
#main_navbar ul li:last-of-type {
margin-top: auto;
} }
.nav-item { .nav-item {
aspect-ratio: 1/1; flex-direction: row;
text-align: left;
justify-content: flex-start;
gap: 0.5rem;
padding: 1rem;
font-weight: 500;
font-size: 0.8rem;
border-radius: 0;
} }
.nav-item__indicator { .nav-item__indicator {
width: 0.25rem; width: 0.25rem;
@ -1054,13 +1210,29 @@ h3 {
align-items: flex-start; align-items: flex-start;
grid-template-columns: 45% 1fr; grid-template-columns: 45% 1fr;
} }
#smart_contract_creation_templates {
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
}
#smart_contract_deposit_form,
#smart_contract_participate_form,
#smart_contract_update_form,
#smart_contract_trigger_form {
width: min(36rem, 100%);
margin: auto;
--gap: 1.5rem;
}
#smart_contract_creation_form::part(form) {
gap: 1.5rem;
margin: auto;
width: min(36rem, 100%);
}
#smart_contract_creation_form::part(form).split-layout {
grid-template-columns: 1fr 1.5fr;
align-items: flex-start;
width: min(50rem, 100%);
}
} }
@media screen and (min-width: 64rem) { @media screen and (min-width: 64rem) {
#main_card {
border-radius: 0.5rem;
height: 90vh;
width: 80vw;
}
#transactions_scroller { #transactions_scroller {
grid-template-columns: 22rem 1fr; grid-template-columns: 22rem 1fr;
align-items: flex-start; align-items: flex-start;

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -70,6 +70,9 @@ body[data-theme="dark"] {
sm-popup::part(popup) { sm-popup::part(popup) {
background-color: rgba(var(--foreground-color), 1); background-color: rgba(var(--foreground-color), 1);
} }
::-webkit-calendar-picker-indicator {
filter: invert(1);
}
} }
p, p,
@ -109,11 +112,9 @@ button,
border-radius: 0.3rem; border-radius: 0.3rem;
justify-content: center; justify-content: center;
text-transform: capitalize; text-transform: capitalize;
&:focus-visible { &:focus-visible {
outline: var(--accent-color) solid medium; outline: var(--accent-color) solid medium;
} }
&:not(:disabled) { &:not(:disabled) {
cursor: pointer; cursor: pointer;
} }
@ -236,6 +237,17 @@ sm-chip {
color: rgba(var(--background-color), 1); color: rgba(var(--background-color), 1);
} }
} }
sm-select {
&::part(options) {
max-height: 40vh;
}
}
sm-option {
flex-shrink: 0;
&::part(option) {
grid-template-columns: none;
}
}
ul { ul {
list-style: none; list-style: none;
} }
@ -251,7 +263,6 @@ ul {
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; word-wrap: break-word;
word-break: break-word; word-break: break-word;
hyphens: auto;
} }
.full-bleed { .full-bleed {
@ -296,6 +307,9 @@ h3 {
.flex { .flex {
display: flex; display: flex;
} }
.flex-1 {
flex: 1;
}
.flex-wrap { .flex-wrap {
flex-wrap: wrap; flex-wrap: wrap;
} }
@ -343,6 +357,9 @@ h3 {
.align-end { .align-end {
align-items: flex-end; align-items: flex-end;
} }
.align-content-center {
align-content: center;
}
.text-center { .text-center {
text-align: center; text-align: center;
@ -359,6 +376,9 @@ h3 {
.justify-right { .justify-right {
margin-left: auto; margin-left: auto;
} }
.justify-items-center {
justify-items: center;
}
.align-self-center { .align-self-center {
align-self: center; align-self: center;
@ -391,6 +411,19 @@ h3 {
height: 100%; height: 100%;
} }
.margin-left-0-5 {
margin-left: 0.5rem;
}
.margin-left-auto {
margin-left: auto;
}
.margin-right-0-5 {
margin-right: 0.5rem;
}
.margin-right-auto {
margin-right: auto;
}
.ripple { .ripple {
height: 8rem; height: 8rem;
width: 8rem; width: 8rem;
@ -453,16 +486,6 @@ h3 {
fill: rgba(var(--text-color), 0.8); fill: rgba(var(--text-color), 0.8);
flex-shrink: 0; flex-shrink: 0;
} }
.margin-right-0-5 {
margin-right: 0.5rem;
}
.margin-left-0-5 {
margin-left: 0.5rem;
}
.margin-left-auto {
margin-left: auto;
}
.icon-button { .icon-button {
padding: 0.6rem; padding: 0.6rem;
@ -509,6 +532,21 @@ h3 {
} }
} }
#loader {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(var(--foreground-color), 1);
z-index: 100;
display: grid;
place-content: center;
place-items: center;
gap: 1rem;
text-align: center;
}
#show_character_count { #show_character_count {
font-size: 0.8rem; font-size: 0.8rem;
margin-left: auto; margin-left: auto;
@ -523,7 +561,7 @@ h3 {
} }
#main_header { #main_header {
padding: 1rem 1.5rem; padding: 0.6rem 1rem;
} }
#main_card { #main_card {
display: flex; display: flex;
@ -558,6 +596,7 @@ h3 {
display: flex; display: flex;
flex: 1; flex: 1;
width: 100%; width: 100%;
height: 100%;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -565,22 +604,17 @@ h3 {
color: var(--text-color); color: var(--text-color);
font-size: 0.7rem; font-size: 0.7rem;
border-radius: 0.3rem; border-radius: 0.3rem;
text-align: center;
.icon { .icon {
transition: transform 0.2s; transition: transform 0.2s;
} }
&__title { &__title {
margin-top: 0.3rem;
transition: opacity 0.2s, transform 0.2s; transition: opacity 0.2s, transform 0.2s;
} }
&--active { &--active {
color: var(--accent-color); color: var(--accent-color);
.icon { .icon {
fill: var(--accent-color); fill: var(--accent-color);
transform: translateY(50%);
}
.nav-item__title {
transform: translateY(100%);
opacity: 0;
} }
} }
&__indicator { &__indicator {
@ -913,7 +947,112 @@ h3 {
} }
} }
#settings { #smartcontracts {
display: grid;
min-width: 0;
height: 100%;
& > * {
grid-area: 1/1;
}
fieldset {
padding: 0.5rem;
border-radius: 0.5rem;
border: solid 1px rgba(var(--text-color), 0.3);
legend {
padding: 0 0.5rem;
}
}
legend,
.label {
font-size: 0.8rem;
color: rgba(var(--text-color), 0.8);
}
label {
padding: 0.3rem 0.5rem;
&:has(input:not(:disabled):not(:checked)) {
cursor: pointer;
}
}
input[type="radio"] {
height: 1.1em;
width: 1.1em;
margin-right: 0.5rem;
accent-color: var(--accent-color);
}
input[type="datetime-local"] {
width: 100%;
padding: 0.8rem 0.6rem;
border: none;
border-radius: 0.5rem;
font-weight: 500;
font-family: inherit;
font-size: inherit;
color: inherit;
background-color: rgba(var(--text-color), 0.06);
&:focus {
outline: none;
box-shadow: 0 0 0 0.1rem var(--accent-color);
}
}
sm-input:not([placeholder]) {
--min-height: 3rem;
}
}
.smart-contract-action {
flex-direction: column;
white-space: normal;
gap: 0.5rem;
color: rgba(var(--text-color), 0.8);
width: 5rem;
font-size: 0.8rem;
aspect-ratio: 1/1;
border-radius: 0.5rem;
border: solid 1px rgba(var(--text-color), 0.3);
.icon {
fill: var(--accent-color);
}
}
#smart_contract_creation_templates {
display: grid;
gap: 0.5rem;
li {
display: flex;
flex: 1;
}
}
.smart-contract-template {
display: grid;
grid-template-areas: "heading arrow" "description arrow";
grid-template-columns: 1fr auto;
padding: max(1rem, 2vw);
border: solid 1px rgba(var(--text-color), 0.3);
border-radius: 0.5rem;
gap: 0.5rem;
text-align: start;
white-space: normal;
width: 100%;
justify-content: flex-start;
h4 {
grid-area: heading;
}
p {
grid-area: description;
font-weight: 400;
line-height: 1.3;
font-size: 0.9rem;
color: rgba(var(--text-color), 0.8);
text-transform: none;
}
.icon {
margin-top: auto;
grid-area: arrow;
border: solid 1px rgba(var(--text-color), 0.3);
border-radius: 3rem;
padding: 0.4rem;
height: 2rem;
width: 2rem;
fill: var(--accent-color);
}
} }
@media screen and (max-width: 40rem) { @media screen and (max-width: 40rem) {
@ -927,12 +1066,22 @@ h3 {
#primary_actions_wrapper { #primary_actions_wrapper {
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
} }
.nav-item {
&__title {
margin-top: 0.3rem;
}
&--active {
.icon {
transform: translateY(50%);
}
.nav-item__title {
transform: translateY(100%);
opacity: 0;
}
}
}
} }
@media screen and (min-width: 40rem) { @media screen and (min-width: 40rem) {
body {
// background: url("back.png") no-repeat;
background-size: cover;
}
sm-popup { sm-popup {
--width: 24rem; --width: 24rem;
} }
@ -950,13 +1099,8 @@ h3 {
display: grid; display: grid;
grid-template-columns: auto 1fr; grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
grid-template-areas: "nav header" "nav main"; grid-template-areas: "header header" "nav main";
position: relative; position: relative;
overflow: hidden;
box-shadow: 0 0.1rem 0.2rem rgba(0, 0, 0, 0.05),
0 1rem 3rem rgba(0, 0, 0, 0.2);
// backdrop-filter: blur(2rem);
background-color: rgba(var(--foreground-color), 0.9);
} }
#main_header { #main_header {
grid-area: header; grid-area: header;
@ -972,16 +1116,17 @@ h3 {
height: 100%; height: 100%;
ul { ul {
flex-direction: column; flex-direction: column;
gap: 0.5rem;
padding: 0.3rem;
li:last-of-type {
margin-top: auto;
}
} }
} }
.nav-item { .nav-item {
aspect-ratio: 1/1; flex-direction: row;
text-align: left;
justify-content: flex-start;
gap: 0.5rem;
padding: 1rem;
font-weight: 500;
font-size: 0.8rem;
border-radius: 0;
&__indicator { &__indicator {
width: 0.25rem; width: 0.25rem;
height: 50%; height: 50%;
@ -1004,13 +1149,30 @@ h3 {
grid-template-columns: 45% 1fr; grid-template-columns: 45% 1fr;
} }
} }
#smart_contract_creation_templates {
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
}
#smart_contract_deposit_form,
#smart_contract_participate_form,
#smart_contract_update_form,
#smart_contract_trigger_form {
width: min(36rem, 100%);
margin: auto;
--gap: 1.5rem;
}
#smart_contract_creation_form::part(form) {
gap: 1.5rem;
margin: auto;
width: min(36rem, 100%);
&.split-layout {
grid-template-columns: 1fr 1.5fr;
align-items: flex-start;
width: min(50rem, 100%);
}
}
} }
@media screen and (min-width: 64rem) { @media screen and (min-width: 64rem) {
#main_card {
border-radius: 0.5rem;
height: 90vh;
width: 80vw;
}
#transactions_scroller { #transactions_scroller {
grid-template-columns: 22rem 1fr; grid-template-columns: 22rem 1fr;
align-items: flex-start; align-items: flex-start;

View File

@ -14,7 +14,8 @@
<script> <script>
/* Constants for FLO blockchain operations !!Make sure to add this at begining!! */ /* Constants for FLO blockchain operations !!Make sure to add this at begining!! */
const floGlobals = { const floGlobals = {
blockchain: "FLO" blockchain: "FLO",
tokenApiUrl: 'https://ranchimallflo.duckdns.org'
} }
</script> </script>
<script src="scripts/components.js" defer></script> <script src="scripts/components.js" defer></script>
@ -34,9 +35,23 @@
pinnedIds: {}, pinnedIds: {},
transactions: {} transactions: {}
} }
compactIDB.initDB("FLOwebWallet", IDBObjects).then(result => { loader()
showPage(window.location.hash) compactIDB.initDB("FLOwebWallet", IDBObjects).then(async result => {
render.savedIds(); render.savedIds();
if (!floGlobals.tokens || !floGlobals.smartContracts) {
fetchJSON(`${floGlobals.tokenApiUrl}/api/v2/tokenSmartContractList`).then(({ tokens, smartContracts }) => {
floGlobals.tokens = tokens.sort((a, b) => a.localeCompare(b))
floGlobals.smartContracts = smartContracts
.filter(sc => sc.status === 'active')
.sort((a, b) => a.contractName.localeCompare(b.contractName))
routeTo(window.location.hash, { firstLoad: true })
}).catch(e => {
console.error(e)
notify('Error fetching tokens', 'error')
}).finally(() => {
loader(false)
})
}
console.log(result) console.log(result)
}).catch(error => console.error(error)) }).catch(error => console.error(error))
} }
@ -53,6 +68,12 @@
<button class="button button--primary confirm-button">OK</button> <button class="button button--primary confirm-button">OK</button>
</div> </div>
</sm-popup> </sm-popup>
<div id="loader" class="hidden">
<sm-spinner></sm-spinner>
<p>
Loading FLO Wallet
</p>
</div>
<div id="main_card"> <div id="main_card">
<header id="main_header" class="flex align-center space-between"> <header id="main_header" class="flex align-center space-between">
<div class="flex align-center"> <div class="flex align-center">
@ -342,7 +363,7 @@
</label> </label>
</sm-input> </sm-input>
<sm-input id="receiver" class="w-100" placeholder="Receiver's FLO address" <sm-input id="receiver" class="w-100" placeholder="Receiver's FLO address"
error-text="Invalid FLO address" data-flo-id="" animate required> error-text="Invalid FLO address" data-flo-address="" animate required>
<button slot="right" class="icon-only" onclick="openPopup('saved_ids_popup')" <button slot="right" class="icon-only" onclick="openPopup('saved_ids_popup')"
title="Select from saved IDs"> title="Select from saved IDs">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" <svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
@ -377,6 +398,184 @@
</div> </div>
</sm-form> </sm-form>
</div> </div>
<div id="smartcontracts" class="page hidden">
<div class="grid gap-2">
<div class="grid full-bleed">
<h3>Smart Contracts</h3>
<p>
Create, participate and manage smart contracts on FLO blockchain.
</p>
</div>
<div class="grid gap-1">
<h4>Actions</h4>
<ul id="smart_contract_actions" class="flex align-center flex-wrap gap-0-5">
<li>
<a class="button smart-contract-action" href="#/smartcontracts/deposit">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z" />
</svg>
<span>Deposit</span>
</a>
</li>
<li>
<a class="button smart-contract-action" href="#/smartcontracts/participate">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z" />
</svg>
<span>Participate</span>
</a>
</li>
<li>
<a class="button smart-contract-action" href="#/smartcontracts/updateprice">
<svg class="icon" xmlns="http://www.w3.org/2000/svg"
enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<rect fill="none" height="24" width="24" />
<path
d="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10s10-4.48,10-10C22,6.48,17.52,2,12,2z M12.06,19v-2.01c-0.02,0-0.04,0-0.06,0 c-1.28,0-2.56-0.49-3.54-1.46c-1.71-1.71-1.92-4.35-0.64-6.29l1.1,1.1c-0.71,1.33-0.53,3.01,0.59,4.13c0.7,0.7,1.62,1.03,2.54,1.01 v-2.14l2.83,2.83L12.06,19z M16.17,14.76l-1.1-1.1c0.71-1.33,0.53-3.01-0.59-4.13C13.79,8.84,12.9,8.5,12,8.5c-0.02,0-0.04,0-0.06,0 v2.15L9.11,7.83L11.94,5v2.02c1.3-0.02,2.61,0.45,3.6,1.45C17.24,10.17,17.45,12.82,16.17,14.76z" />
</svg>
<span>Update price</span>
</a>
</li>
<li>
<a class="button smart-contract-action" href="#/smartcontracts/trigger">
<svg class="icon" xmlns="http://www.w3.org/2000/svg"
enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<rect fill="none" height="24" width="24" />
<path
d="M14.59,7.41L18.17,11H6v2h12.17l-3.59,3.59L16,18l6-6l-6-6L14.59,7.41z M2,6v12h2V6H2z" />
</svg>
<span>Trigger</span>
</a>
</li>
</ul>
</div>
<div class="grid gap-1">
<h4>Creation templates</h4>
<ul id="smart_contract_creation_templates">
<li>
<a class="button smart-contract-template"
href="#/smartcontracts/create?type=one-time-event&subtype=time-trigger">
<h4>
Time trigger (One time event)
</h4>
<p>
Suitable for time-specific events like crowdfunding.
</p>
<svg class="icon margin-left-0-5" xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z" />
</svg>
</a>
</li>
<li>
<a class="button smart-contract-template"
href="#/smartcontracts/create?type=one-time-event&subtype=external-trigger">
<h4>
External trigger (One time event)
</h4>
<p>
Suitable for externally triggered events like voting.
</p>
<svg class="icon margin-left-0-5" xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z" />
</svg>
</a>
</li>
<li>
<a class="button smart-contract-template"
href="#/smartcontracts/create?type=continuous-event&subtype=tokenswap">
<h4>
Continuous event
</h4>
<p>
Suitable for ongoing processes involving multiple participants, such as
token swaps.
</p>
<svg class="icon margin-left-0-5" xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z" />
</svg>
</a>
</li>
</ul>
</div>
</div>
<div class="grid hidden gap-1">
<div class="flex gap-0-5 align-center">
<a class="button icon-only" href="#/smartcontracts">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
</svg>
</a>
<h4>Create smart contract</h4>
</div>
<sm-form id="smart_contract_creation_form"></sm-form>
</div>
<div class="grid hidden gap-1">
<div class="flex gap-0-5 align-center">
<a class="button icon-only" href="#/smartcontracts">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
</svg>
</a>
<h4>Deposit</h4>
</div>
<sm-form id="smart_contract_deposit_form"></sm-form>
</div>
<div class="grid hidden gap-1">
<div class="flex gap-0-5 align-center">
<a class="button icon-only" href="#/smartcontracts">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
</svg>
</a>
<h4>Participate</h4>
</div>
<sm-form id="smart_contract_participate_form"></sm-form>
</div>
<div class="grid hidden gap-1">
<div class="flex gap-0-5 align-center">
<a class="button icon-only" href="#/smartcontracts">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
</svg>
</a>
<h4>Update price</h4>
</div>
<sm-form id="smart_contract_update_form"></sm-form>
</div>
<div class="grid hidden gap-1">
<div class="flex gap-0-5 align-center">
<a class="button icon-only" href="#/smartcontracts">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
</svg>
</a>
<h4>Trigger</h4>
</div>
<sm-form id="smart_contract_trigger_form"></sm-form>
</div>
</div>
<div id="settings" class="page hidden gap-2"> <div id="settings" class="page hidden gap-2">
<h3>Settings</h3> <h3>Settings</h3>
<section class="grid gap-1"> <section class="grid gap-1">
@ -422,6 +621,23 @@
<span class="nav-item__title">Send</span> <span class="nav-item__title">Send</span>
</a> </a>
</li> </li>
<li class="hidden">
<a href="#/smartcontracts" class="nav-item interact" title='Send FLO data'>
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24"
height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M0,0h24v24H0V0z" fill="none" />
<g>
<path
d="M19.5,3.5L18,2l-1.5,1.5L15,2l-1.5,1.5L12,2l-1.5,1.5L9,2L7.5,3.5L6,2v14H3v3c0,1.66,1.34,3,3,3h12c1.66,0,3-1.34,3-3V2 L19.5,3.5z M19,19c0,0.55-0.45,1-1,1s-1-0.45-1-1v-3H8V5h11V19z" />
<rect height="2" width="6" x="9" y="7" />
<rect height="2" width="2" x="16" y="7" />
<rect height="2" width="6" x="9" y="10" />
<rect height="2" width="2" x="16" y="10" />
</g>
</svg>
<span class="nav-item__title">Smart Contracts</span>
</a>
</li>
<li> <li>
<a href="#/settings" class="nav-item interact"> <a href="#/settings" class="nav-item interact">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" <svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24"
@ -453,7 +669,7 @@
</header> </header>
<sm-form> <sm-form>
<sm-input id="flo_addr_to_save" placeholder="FLO address" error-text="Invalid FLO address" autofocus <sm-input id="flo_addr_to_save" placeholder="FLO address" error-text="Invalid FLO address" autofocus
data-flo-id animate required> data-flo-address animate required>
</sm-input> </sm-input>
<sm-input id="label_to_save" placeholder="Name" animate></sm-input> <sm-input id="label_to_save" placeholder="Name" animate></sm-input>
<button class="button button--primary cta" type="submit" onclick="saveFloId()" disabled>Add</button> <button class="button button--primary cta" type="submit" onclick="saveFloId()" disabled>Add</button>
@ -539,7 +755,7 @@
<sm-copy id="recovered_flo_id"></sm-copy> <sm-copy id="recovered_flo_id"></sm-copy>
</div> </div>
<sm-input id="retrieve_flo_id_field" type="password" error-text="Invalid private key" <sm-input id="retrieve_flo_id_field" type="password" error-text="Invalid private key"
placeholder="Private key" class="password-field" required autofocus> placeholder="Private key" class="password-field" data-private-key required autofocus>
<label slot="right" class="interact"> <label slot="right" class="interact">
<input type="checkbox" class="hidden" readonly onchange="togglePrivateKeyVisibility(this)"> <input type="checkbox" class="hidden" readonly onchange="togglePrivateKeyVisibility(this)">
<svg class="icon invisible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" <svg class="icon invisible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
@ -576,7 +792,7 @@
</header> </header>
<sm-form id="check_balance_form"> <sm-form id="check_balance_form">
<sm-input id="check_balance_field" type="text" error-text="Invalid FLO address" placeholder="FLO address" <sm-input id="check_balance_field" type="text" error-text="Invalid FLO address" placeholder="FLO address"
data-flo-id="" required autofocus> data-flo-address="" required autofocus>
</sm-input> </sm-input>
<div class="multi-state-button"> <div class="multi-state-button">
<button id="check_balance_button" class="button button--primary cta" type="submit" <button id="check_balance_button" class="button button--primary cta" type="submit"
@ -920,7 +1136,7 @@
if ((tem = ua.match(/version\/(\d+)/i)) != null) M.splice(1, 1, tem[1]); if ((tem = ua.match(/version\/(\d+)/i)) != null) M.splice(1, 1, tem[1]);
return M.join(' '); return M.join(' ');
} }
window.addEventListener('hashchange', e => showPage(window.location.hash)) window.addEventListener('hashchange', e => routeTo(window.location.hash))
window.addEventListener("load", () => { window.addEventListener("load", () => {
const [browserName, browserVersion] = detectBrowser().split(' '); const [browserName, browserVersion] = detectBrowser().split(' ');
const supportedVersions = { const supportedVersions = {
@ -936,8 +1152,6 @@
notify('Browser is not fully compatible, some features may not work. for best experience please use Chrome, Edge, Firefox or Safari', 'error') notify('Browser is not fully compatible, some features may not work. for best experience please use Chrome, Edge, Firefox or Safari', 'error')
} }
document.body.classList.remove('hidden') document.body.classList.remove('hidden')
document.querySelectorAll('sm-input[data-flo-id]').forEach(input => input.customValidation = floCrypto.validateFloID)
document.querySelectorAll('sm-input[data-private-key]').forEach(input => input.customValidation = floCrypto.getPubKeyHex)
document.addEventListener('keyup', (e) => { document.addEventListener('keyup', (e) => {
if (e.key === 'Escape') { if (e.key === 'Escape') {
closePopup() closePopup()
@ -1000,8 +1214,8 @@
} }
let tempData let tempData
async function showPage(targetPage, options = {}) { async function routeTo(targetPage, options = {}) {
const { firstLoad, hashChange, isPreview } = options const { firstLoad, hashChange } = options
let pageId let pageId
let params = {} let params = {}
let searchParams let searchParams
@ -1022,7 +1236,6 @@
params = Object.fromEntries(urlSearchParams.entries()); params = Object.fromEntries(urlSearchParams.entries());
} }
if (pagesData.lastPage !== pageId) { if (pagesData.lastPage !== pageId) {
pagesData.lastPage = pageId
pagesData.wildcards = wildcards pagesData.wildcards = wildcards
} }
if (params) if (params)
@ -1038,6 +1251,170 @@
notify('Invalid Flo ID', 'error') notify('Invalid Flo ID', 'error')
} }
break; break;
case 'smartcontracts':
const [subpage] = wildcards
const { type, subtype } = params
if (subpage) {
switch (subpage) {
case 'create':
if (!type && !subtype)
getRef('smart_contract_creation_form').classList.add('split-layout')
else
getRef('smart_contract_creation_form').classList.remove('split-layout')
renderElem(getRef('smart_contract_creation_form'), render.contractCreationForm(type, subtype))
showChildElement('smartcontracts', 1, { entry: slideInLeft, exit: slideOutLeft })
break;
case 'deposit': {
const filteredSmartContracts = filterSmartContracts({ type: 'continuos-event' })
renderElem(getRef('smart_contract_deposit_form'), html`
<div class="grid gap-0-5">
<span class="label">FLO private key</span>
<sm-input class="password-field" type="password" error-text="Invalid private key" data-private-key required>
<label slot="right" class="interact">
<input type="checkbox" class="hidden" readonly="" onchange="togglePrivateKeyVisibility(this)">
<svg class="icon invisible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Hide password</title> <path d="M0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0z" fill="none"></path> <path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"></path> </svg>
<svg class="icon visible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Show password</title> <path d="M0 0h24v24H0z" fill="none"></path> <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"></path> </svg>
</label>
</sm-input>
</div>
<div class="grid gap-0-5">
<span class="label">Select smart contract</span>
<sm-select>
${render.availableSmartContractOptions(filteredSmartContracts)}
</sm-select>
</div>
<div class="grid gap-0-5">
<span class="label">Amount</span>
<sm-input type="number" required></sm-input>
</div>
<div class="grid gap-0-5">
<span class="label">Expiration (Time after which unspent amount will be returned)</span>
<input type="datetime-local" required>
</div>
<div class="multi-state-button">
<button class="button button--primary" type="submit" disabled>Deposit</button>
</div>
`)
showChildElement('smartcontracts', 2, { entry: slideInLeft, exit: slideOutLeft })
break;
}
case 'participate': {
const filteredSmartContracts = filterSmartContracts()
renderElem(getRef('smart_contract_participate_form'), html`
<div class="grid gap-0-5">
<span class="label">FLO private key</span>
<sm-input class="password-field" type="password" error-text="Invalid private key" data-private-key="" required="">
<label slot="right" class="interact">
<input type="checkbox" class="hidden" readonly="" onchange="togglePrivateKeyVisibility(this)">
<svg class="icon invisible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Hide password</title> <path d="M0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0z" fill="none"></path> <path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"></path> </svg>
<svg class="icon visible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Show password</title> <path d="M0 0h24v24H0z" fill="none"></path> <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"></path> </svg>
</label>
</sm-input>
</div>
<div class="grid gap-0-5">
<span class="label">Select smart contract</span>
<sm-select>
${render.availableSmartContractOptions(filteredSmartContracts)}
</sm-select>
</div>
<div class="grid gap-0-5">
<span class="label">Amount</span>
<sm-input type="number" required></sm-input>
</div>
<div class="multi-state-button">
<button class="button button--primary" type="submit" disabled>Send</button>
</div>
`)
showChildElement('smartcontracts', 3, { entry: slideInLeft, exit: slideOutLeft })
break;
}
case 'updateprice': {
const filteredSmartContracts = filterSmartContracts({ type: 'continuos-event', dynamic: true })
const { contractName, contractAddress, oracle_address, price, acceptingToken } = filteredSmartContracts[0] || {}
showChildElement('smartcontracts', 4, !firstLoad ? { entry: slideInLeft, exit: slideOutLeft } : {})
console.log('filteredSmartContracts', filteredSmartContracts)
renderElem(getRef('smart_contract_update_form'), html`
<div class="grid gap-0-5">
<span class="label">Oracle FLO Address</span>
<sm-copy value=${oracle_address}></sm-copy>
</div>
<div class="grid gap-0-5">
<span class="label">Oracle FLO private key</span>
<sm-input id="oracle_private_key" class="password-field" type="password" error-text="Invalid private key" data-private-key="" required="">
<label slot="right" class="interact">
<input type="checkbox" class="hidden" readonly="" onchange="togglePrivateKeyVisibility(this)">
<svg class="icon invisible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Hide password</title> <path d="M0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0z" fill="none"></path> <path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"></path> </svg>
<svg class="icon visible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Show password</title> <path d="M0 0h24v24H0z" fill="none"></path> <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"></path> </svg>
</label>
</sm-input>
</div>
<div class="grid gap-0-5">
<span class="label">Select smart contract</span>
<sm-select id="selected_smart_contract" onchange=${handleSmartContractSelection}>
${render.availableSmartContractOptions(filteredSmartContracts)}
</sm-select>
</div>
<p>
<span class="label">Current price: </span>
<span class="label">${price} ${acceptingToken}</span>
</p>
<div class="grid gap-0-5">
<span class="label">Updated price (${acceptingToken})</span>
<sm-input id="updated_price" type="number" step="0.00000001" min="0.00000001" error-text="Minimum 0.00000001 required" required></sm-input>
</div>
<div class="multi-state-button">
<button class="button button--primary" type="submit" onclick=${updatePrice} disabled>Update</button>
</div>
`)
break;
}
case 'trigger': {
const filteredSmartContracts = filterSmartContracts({ type: 'one-time-event', subType: 'external-event' })
renderElem(getRef('smart_contract_trigger_form'), html`
<div class="grid gap-0-5">
<span class="label">FLO private key</span>
<sm-input class="password-field" type="password" error-text="Invalid private key" data-private-key="" required="">
<label slot="right" class="interact">
<input type="checkbox" class="hidden" readonly="" onchange="togglePrivateKeyVisibility(this)">
<svg class="icon invisible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Hide password</title> <path d="M0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0z" fill="none"></path> <path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"></path> </svg>
<svg class="icon visible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Show password</title> <path d="M0 0h24v24H0z" fill="none"></path> <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"></path> </svg>
</label>
</sm-input>
</div>
<div class="grid gap-0-5">
<span class="label">Select smart contract</span>
<sm-select>
${render.availableSmartContractOptions(filteredSmartContracts)}
</sm-select>
</div>
<fieldset>
<legend>Select outcome</legend>
<div class="grid gap-0-5">
<label>
<input type="radio" name="outcome" value="1" checked>
<span>Outcome 1</span>
</label>
<label>
<input type="radio" name="outcome" value="2">
<span>Outcome 2</span>
</label>
</div>
</fieldset>
<div class="multi-state-button">
<button class="button button--primary" type="submit" disabled>Trigger</button>
</div>
`)
showChildElement('smartcontracts', 5, { entry: slideInLeft, exit: slideOutLeft })
break;
}
default:
notify('Invalid page', 'error')
}
} else {
showChildElement('smartcontracts', 0, { entry: slideInRight, exit: slideOutRight })
renderElem(getRef('smart_contract_creation_form'), html``)
}
break
default: default:
getRef('transactions_list').innerHTML = '' getRef('transactions_list').innerHTML = ''
scrollToTopObserver.disconnect() scrollToTopObserver.disconnect()
@ -1131,9 +1508,14 @@
getRef('main_header').classList.add('hidden') getRef('main_header').classList.add('hidden')
} }
} }
document.querySelectorAll('.page').forEach(page => page.classList.add('hidden')) if (pagesData.lastPage !== pageId) {
getRef(pageId).classList.remove('hidden') document.querySelectorAll('.page').forEach(page => page.classList.add('hidden'))
getRef(pageId).animate([{ opacity: 0 }, { opacity: 1 }], { duration: 300, fill: 'forwards', easing: 'ease' }) getRef(pageId).classList.remove('hidden')
getRef(pageId).animate([{ opacity: 0 }, { opacity: 1 }], { duration: 300, fill: 'forwards', easing: 'ease' })
pagesData.lastPage = pageId
}
document.querySelectorAll('sm-input[data-flo-address]').forEach(input => input.customValidation = floCrypto.validateFloID)
document.querySelectorAll('sm-input[data-private-key]').forEach(input => input.customValidation = floCrypto.getPubKeyHex)
} }
const indicatorObserver = new IntersectionObserver(entries => { const indicatorObserver = new IntersectionObserver(entries => {
@ -1253,22 +1635,136 @@
handleMobileChange(mobileQuery) handleMobileChange(mobileQuery)
// fetch with json response // fetch data and return json
async function fetchJSON(url, options) { async function fetchJSON(url, options = {}) {
const response = await fetch(url, options) const response = await fetch(url, options)
const json = await response.json()
if (response.ok) { if (response.ok) {
return await response.json() return json
} else { } else {
throw new Error(response.statusText) throw new Error(json.description)
} }
} }
const slideInLeft = [
{
opacity: 0,
transform: 'translateX(1rem)'
},
{
opacity: 1,
transform: 'translateX(0)'
}
]
const slideOutLeft = [
{
opacity: 1,
transform: 'translateX(0)'
},
{
opacity: 0,
transform: 'translateX(-1rem)'
},
]
const slideInRight = [
{
opacity: 0,
transform: 'translateX(-1rem)'
},
{
opacity: 1,
transform: 'translateX(0)'
}
]
const slideOutRight = [
{
opacity: 1,
transform: 'translateX(0)'
},
{
opacity: 0,
transform: 'translateX(1rem)'
},
]
const slideInDown = [
{
opacity: 0,
transform: 'translateY(-1rem)'
},
{
opacity: 1,
transform: 'translateY(0)'
},
]
const slideOutDown = [
{
opacity: 1,
transform: 'translateY(0)'
},
{
opacity: 0,
transform: 'translateY(1rem)'
},
]
const slideInUp = [
{
opacity: 0,
transform: 'translateY(1rem)'
},
{
opacity: 1,
transform: 'translateY(0)'
},
]
const slideOutUp = [
{
opacity: 1,
transform: 'translateY(0)'
},
{
opacity: 0,
transform: 'translateY(-1rem)'
},
]
function showChildElement(id, index, options = {}) {
return new Promise((resolve) => {
const { mobileView = false, entry, exit } = options
const animOptions = {
duration: 150,
easing: 'ease',
fill: 'forwards'
}
const parent = typeof id === 'string' ? document.getElementById(id) : id;
const visibleElement = [...parent.children].find(elem => !elem.classList.contains(mobileView ? 'hide-on-mobile' : 'hidden'));
if (visibleElement === parent.children[index]) return;
visibleElement.getAnimations().forEach(anim => anim.cancel())
parent.children[index].getAnimations().forEach(anim => anim.cancel())
if (visibleElement) {
if (exit) {
visibleElement.animate(exit, animOptions).onfinish = () => {
visibleElement.classList.add(mobileView ? 'hide-on-mobile' : 'hidden')
parent.children[index].classList.remove(mobileView ? 'hide-on-mobile' : 'hidden')
if (entry)
parent.children[index].animate(entry, animOptions).onfinish = () => resolve()
}
} else {
visibleElement.classList.add(mobileView ? 'hide-on-mobile' : 'hidden')
parent.children[index].classList.remove(mobileView ? 'hide-on-mobile' : 'hidden')
resolve()
}
} else {
parent.children[index].classList.remove(mobileView ? 'hide-on-mobile' : 'hidden')
parent.children[index].animate(entry, animOptions).onfinish = () => resolve()
}
})
}
</script> </script>
<script> <script>
//UI for webWallet //UI for webWallet
const render = { const render = {
savedIdCard(floID, name) { savedIdCard(floID, name) {
const clone = getRef('saved_id_template').content.cloneNode(true).firstElementChild; const clone = getRef('saved_id_template').content.cloneNode(true).firstElementChild;
clone.dataset.floId = floID; clone.dataset.floAddress = floID;
clone.dataset.name = name; clone.dataset.name = name;
clone.querySelector('.saved-id__initial').textContent = name[0]; clone.querySelector('.saved-id__initial').textContent = name[0];
clone.querySelector('.saved-id__label').textContent = name; clone.querySelector('.saved-id__label').textContent = name;
@ -1291,7 +1787,7 @@
savedIdPickerCard(floID, name) { savedIdPickerCard(floID, name) {
return createElement('li', { return createElement('li', {
className: 'saved-id grid interact', className: 'saved-id grid interact',
attributes: { 'tabindex': '0', 'data-flo-id': floID }, attributes: { 'tabindex': '0', 'data-flo-address': floID },
innerHTML: ` innerHTML: `
<div class="saved-id__initial">${name[0]}</div> <div class="saved-id__initial">${name[0]}</div>
<div class="grid gap-0-5"> <div class="grid gap-0-5">
@ -1378,6 +1874,129 @@
} catch (err) { } catch (err) {
notify(err, 'error'); notify(err, 'error');
} }
},
availableAssetOptions() {
return (floGlobals.tokens || []).map(token => html` <sm-option value=${token}>${token}</sm-option> `)
},
availableSmartContractOptions(smartContracts = []) {
return smartContracts
.map(({ contractName, contractAddress }) => html`
<sm-option class="breakable" value=${`${contractName}_${contractAddress}`}>${`${contractName}-${contractAddress}`}</sm-option>
`)
},
contractCreationForm(type, subtype) {
return html`
${!type && !subtype ? html`
<div class="grid gap-1-5">
<fieldset class="grid gap-0-5">
<legend>Type</legend>
<label>
<input type="radio" name="contract-type" value="one-time-event" ?checked=${type === 'one-time-event'} ?disabled=${type !== 'one-time-event'}>
<span>One time event</span>
</label>
<label>
<input type="radio" name="contract-type" value="continuous-event" ?checked=${type === 'continuous-event'} ?disabled=${type !== 'continuous-event'}>
<span>Continuous event</span>
</label>
</fieldset>
<fieldset class="grid gap-0-5">
<legend>Subtype</legend>
<label>
<input type="radio" name="contract-subtype" value="time-trigger" ?checked=${subtype === 'time-trigger'} ?disabled=${subtype !== 'time-trigger'}>
<span>Time trigger</span>
</label>
<label>
<input type="radio" name="contract-subtype" value="external-trigger" ?checked=${subtype === 'external-trigger'} ?disabled=${subtype !== 'external-trigger'}>
<span>External trigger</span>
</label>
<label>
<input type="radio" name="contract-subtype" value="tokenswap" ?checked=${subtype === 'tokenswap'} ?disabled=${subtype !== 'tokenswap'}>
<span>Token Swap</span>
</label>
</fieldset>
</div>
` : ''}
<div class="grid gap-1-5">
<div class="grid gap-0-5">
<span class="label">Contract name</span>
<sm-input required> </sm-input>
</div>
${type === 'one-time-event' ? html`
<div class="grid gap-0-5">
<span class="label">Asset</span>
<sm-select>
${render.availableAssetOptions()}
</sm-select>
</div>
${subtype === 'time-trigger' ? html`
<div class="grid gap-0-5">
<span class="label">Payee FLO addresses</span>
<div class="flex gap-0-5">
<sm-input class="flex w-100" placeholder="FLO address" data-flo-address required> </sm-input>
<sm-input placeholder="Share (%)" required> </sm-input>
</div>
</div>
` : html`
<div class="grid gap-0-5">
<span class="label">User choices</span>
<div class="grid gap-0-3">
<sm-input class="user-choice" placeholder="Choice 1" required> </sm-input>
<sm-input class="user-choice" placeholder="Choice 2" required> </sm-input>
</div>
<button class="justify-start gap-0-5">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
Add choice
</button>
</div>
`}
<div class="grid gap-0-5">
<span class="label">Expiration</span>
<input type="datetime-local" required>
</div>
<div class="grid gap-0-5">
<span class="label">Participation amount (optional)</span>
<sm-input type="number"> </sm-input>
</div>
<div class="grid gap-0-5">
<span class="label">Min. subscription amount (optional)</span>
<sm-input type="number"> </sm-input>
</div>
<div class="grid gap-0-5">
<span class="label">Max. subscription amount (optional)</span>
<sm-input type="number"> </sm-input>
</div>
` : html`
<div class="grid gap-0-5">
<span class="label">Price</span>
<sm-input type="number"> </sm-input>
</div>
<fieldset class="grid gap-0-5" onchange=${handlePriceTypeChange}>
<legend>Price type</legend>
<label>
<input type="radio" name="price-type" value="static" checked>
<span>Static</span>
</label>
<label>
<input type="radio" name="price-type" value="dynamic">
<span>Dynamic</span>
</label>
</fieldset>
<div class="grid gap-0-5">
<span class="label">Input token</span>
<sm-select>
${render.availableAssetOptions()}
</sm-select>
</div>
<div class="grid gap-0-5">
<span class="label">Output token</span>
<sm-select>
${render.availableAssetOptions()}
</sm-select>
</div>
`}
<button class="button button--primary" type="submit" disabled>Create</button>
</div>
`
} }
} }
let transactionsLazyLoader let transactionsLazyLoader
@ -1391,12 +2010,12 @@
delegate(getRef('saved_ids_list'), 'click', '.saved-id', e => { delegate(getRef('saved_ids_list'), 'click', '.saved-id', e => {
if (e.target.closest('.edit-saved')) { if (e.target.closest('.edit-saved')) {
const target = e.target.closest('.saved-id'); const target = e.target.closest('.saved-id');
getRef('edit_saved_id').setAttribute('value', target.dataset.floId) getRef('edit_saved_id').setAttribute('value', target.dataset.floAddress)
getRef('new_addr_label').value = target.dataset.name getRef('new_addr_label').value = target.dataset.name
openPopup('edit_saved_popup') openPopup('edit_saved_popup')
} else if (e.target.closest('.copy-saved-id')) { } else if (e.target.closest('.copy-saved-id')) {
const target = e.target.closest('.saved-id'); const target = e.target.closest('.saved-id');
navigator.clipboard.writeText(target.dataset.floId) navigator.clipboard.writeText(target.dataset.floAddress)
target.dispatchEvent( target.dispatchEvent(
new CustomEvent('copy', { new CustomEvent('copy', {
bubbles: true, bubbles: true,
@ -1405,12 +2024,12 @@
); );
} else { } else {
const target = e.target.closest('.saved-id'); const target = e.target.closest('.saved-id');
window.location.hash = `#/transactions/${target.dataset.floId}` window.location.hash = `#/transactions/${target.dataset.floAddress}`
} }
}) })
delegate(getRef('saved_ids_picker_list'), 'click', '.saved-id', e => { delegate(getRef('saved_ids_picker_list'), 'click', '.saved-id', e => {
const target = e.target.closest('.saved-id'); const target = e.target.closest('.saved-id');
getRef('receiver').value = target.dataset.floId getRef('receiver').value = target.dataset.floAddress
getRef('receiver').focusIn() getRef('receiver').focusIn()
closePopup() closePopup()
}) })
@ -1691,7 +2310,7 @@
if (name == '') if (name == '')
name = 'Unknown'; name = 'Unknown';
compactIDB.writeData('labels', name, getRef('edit_saved_id').value).then((resolve) => { compactIDB.writeData('labels', name, getRef('edit_saved_id').value).then((resolve) => {
const potentialTarget = getRef('saved_ids_list').querySelector(`.saved-id[data-flo-id="${getRef('edit_saved_id').value}"]`) const potentialTarget = getRef('saved_ids_list').querySelector(`.saved-id[data-flo-address="${getRef('edit_saved_id').value}"]`)
if (potentialTarget) { if (potentialTarget) {
potentialTarget.dataset.name = name; potentialTarget.dataset.name = name;
potentialTarget.querySelector('.saved-id__label').textContent = name; potentialTarget.querySelector('.saved-id__label').textContent = name;
@ -1712,7 +2331,7 @@
confirmText: 'Delete', confirmText: 'Delete',
}).then(res => { }).then(res => {
if (res) { if (res) {
const toDelete = getRef('saved_ids_list').querySelector(`.saved-id[data-flo-id="${getRef('edit_saved_id').value}"]`) const toDelete = getRef('saved_ids_list').querySelector(`.saved-id[data-flo-address="${getRef('edit_saved_id').value}"]`)
if (toDelete) if (toDelete)
toDelete.remove(); toDelete.remove();
compactIDB.removeData('labels', getRef('edit_saved_id').value); compactIDB.removeData('labels', getRef('edit_saved_id').value);
@ -1885,6 +2504,143 @@
'--tangerine', '--tangerine',
'--redish-orange', '--redish-orange',
] ]
// async function getContractInfo(name, address) {
// return new Promise((resolve, reject) => {
// if (!name) {
// name = floGlobals.smartContracts[0].contractName
// address = floGlobals.smartContracts[0].contractAddress
// }
// fetchJSON(`${floGlobals.tokenApiUrl}/api/v2/smartContractInfo?contractName=${name}&contractAddress=${address}`)
// .then(info => {
// console.log(info)
// const {
// contractInfo: {
// contractType,
// numberOfDeposits,
// numberOfParticipants,
// priceType,
// oracle_address,
// contractSubtype,
// status,
// expiryTime,
// payeeAddress,
// userChoice,
// tokenIdentification,
// acceptingToken,
// sellingToken,
// contractAmount,
// minimumsubscriptionamount,
// maximumsubscriptionamount,
// totalHonorAmount,
// totalParticipationAmount,
// price
// }, contractAddress,
// contractName
// } = info
// resolve({
// contractName,
// contractAddress,
// contractType,
// contractSubtype,
// status,
// expiryTime,
// payeeAddress,
// userChoices: userChoice,
// token: tokenIdentification,
// acceptingToken,
// sellingToken,
// participationFees: contractAmount,
// minimumsubscriptionamount,
// maximumsubscriptionamount,
// numberOfDeposits,
// numberOfParticipants,
// priceType,
// oracle_address,
// totalHonorAmount,
// totalParticipationAmount,
// price
// })
// }).catch(error => {
// reject(error)
// })
// })
// }
function filterSmartContracts(options) {
const { type, subType, dynamic = false } = options
let filteredSmartContracts = (floGlobals.smartContracts || []);
if (type) {
filteredSmartContracts = filteredSmartContracts.filter(sc => sc.contractType === type)
if (type === 'continuos-event' && dynamic)
filteredSmartContracts = filteredSmartContracts.filter(sc => sc.oracle_address)
}
if (subType)
filteredSmartContracts = filteredSmartContracts.filter(sc => sc.contractSubType === subType)
return filteredSmartContracts
}
function handleSmartContractSelection() {
console.log('handleSmartContractSelection')
}
function updatePrice(e) {
const selectedSmartContract = document.getElementById('selected_smart_contract').value
const [contractName, contractAddress] = (selectedSmartContract).split('_')
const oraclePrivateKey = document.getElementById('oracle_private_key').value
const oracleAddress = floCrypto.getFloID(oraclePrivateKey)
const updatedPrice = parseFloat(document.getElementById('updated_price').value.trim())
const floData = ` {"price-update":{"contract-name": "${contractName}", "contract-address": "${contractAddress}", "price": ${updatedPrice}}} `
console.log(floData)
getConfirmation('Update price', {
message: `Are you sure you want to update the price of ${contractName} to ${updatedPrice} FLO?`,
confirmText: 'Update',
cancelText: 'Cancel'
}).then((res) => {
if (!res) return
buttonLoader(e.target.closest('button'), true)
floBlockchainAPI.writeData(oracleAddress, floData, oraclePrivateKey, contractAddress).then((txid) => {
showTransactionResult(true, txid)
}).catch((error) => {
showTransactionResult(false, error)
}).finally(() => {
buttonLoader(e.target.closest('button'), false)
})
})
}
function handlePriceTypeChange(e) {
switch (e.target.value) {
case 'static':
e.target.closest('sm-form').querySelector('.oracle-address-wrapper')?.remove()
break;
case 'dynamic':
e.target.closest('sm-form').querySelector('.oracle-address-wrapper')?.remove()
e.target.closest('fieldset').after(html.node`
<div class="grid gap-0-5 oracle-address-wrapper">
<span class="label">Oracle FLO address</span>
<sm-input data-flo-address required> </sm-input>
</div>
`)
break;
}
}
function loader(show = true) {
if (show) {
getRef('loader').classList.remove('hidden')
} else {
getRef('loader').animate([
{ transform: 'translateY(0)' },
{ transform: 'translateY(-100%)' }
], {
duration: 300,
easing: 'ease-in-out'
}).onfinish = () =>
getRef('loader').classList.add('hidden')
}
}
</script> </script>
</body> </body>

File diff suppressed because one or more lines are too long