Compare commits
10 Commits
82e2b0993e
...
d56d2fa421
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d56d2fa421 | ||
|
|
6cc13901e2 | ||
|
|
dc335b550a | ||
|
|
c5208b0600 | ||
|
|
4426493304 | ||
|
|
826cb771e4 | ||
|
|
564bd49768 | ||
|
|
06e10d42f9 | ||
|
|
01413468c4 | ||
|
|
80b7d70d86 |
78
css/main.css
78
css/main.css
@ -48,7 +48,7 @@ strong {
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--accent-color);
|
||||
color: inherit;
|
||||
}
|
||||
a:focus-visible {
|
||||
box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1) inset;
|
||||
@ -348,6 +348,12 @@ details .down-arrow {
|
||||
margin-inline: 1.5rem;
|
||||
}
|
||||
|
||||
.wrap-around {
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
sm-chips {
|
||||
--gap: 0.3rem;
|
||||
}
|
||||
@ -367,6 +373,10 @@ sm-chip[selected] {
|
||||
--background: var(--accent-color);
|
||||
}
|
||||
|
||||
sm-input {
|
||||
--border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.multi-state-button {
|
||||
display: grid;
|
||||
text-align: center;
|
||||
@ -381,6 +391,43 @@ sm-chip[selected] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#confirmation_popup,
|
||||
#prompt_popup {
|
||||
flex-direction: column;
|
||||
}
|
||||
#confirmation_popup h4,
|
||||
#prompt_popup h4 {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
#confirmation_popup .flex,
|
||||
#prompt_popup .flex {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.popup__header {
|
||||
position: relative;
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
padding: 0 1.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
.popup__header > * {
|
||||
grid-row: 1;
|
||||
}
|
||||
.popup__header h3,
|
||||
.popup__header h4 {
|
||||
grid-column: 1/-1;
|
||||
justify-self: center;
|
||||
align-self: center;
|
||||
}
|
||||
.popup__header__close {
|
||||
grid-column: 1;
|
||||
margin-left: -1rem;
|
||||
justify-self: flex-start;
|
||||
}
|
||||
|
||||
#loading {
|
||||
position: fixed;
|
||||
display: grid;
|
||||
@ -414,6 +461,10 @@ header {
|
||||
.address-input sm-input {
|
||||
width: 100%;
|
||||
}
|
||||
.address-input button {
|
||||
margin-bottom: auto;
|
||||
height: 3.2rem;
|
||||
}
|
||||
|
||||
#verification_result {
|
||||
display: grid;
|
||||
@ -444,6 +495,31 @@ header {
|
||||
min-width: 5rem;
|
||||
}
|
||||
|
||||
.revoke-card {
|
||||
list-style: none;
|
||||
}
|
||||
.revoke-card label {
|
||||
gap: 1rem;
|
||||
padding: 0.8rem 0;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
.revoke-card input {
|
||||
accent-color: var(--accent-color);
|
||||
height: 1.3em;
|
||||
width: 1.3em;
|
||||
}
|
||||
.revoke-card span:last-of-type {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 40rem) {
|
||||
sm-popup {
|
||||
--width: 24rem;
|
||||
}
|
||||
}
|
||||
.error {
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
2
css/main.min.css
vendored
2
css/main.min.css
vendored
File diff suppressed because one or more lines are too long
@ -48,7 +48,7 @@ strong {
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--accent-color);
|
||||
color: inherit;
|
||||
|
||||
&:focus-visible {
|
||||
box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1) inset;
|
||||
@ -334,6 +334,11 @@ details {
|
||||
.margin-inline-1-5 {
|
||||
margin-inline: 1.5rem;
|
||||
}
|
||||
.wrap-around {
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
sm-chips {
|
||||
--gap: 0.3rem;
|
||||
}
|
||||
@ -350,6 +355,9 @@ sm-chip {
|
||||
--background: var(--accent-color);
|
||||
}
|
||||
}
|
||||
sm-input {
|
||||
--border-radius: 0.5rem;
|
||||
}
|
||||
.multi-state-button {
|
||||
display: grid;
|
||||
text-align: center;
|
||||
@ -363,6 +371,41 @@ sm-chip {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
#confirmation_popup,
|
||||
#prompt_popup {
|
||||
flex-direction: column;
|
||||
h4 {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.flex {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.popup__header {
|
||||
position: relative;
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
padding: 0 1.5rem;
|
||||
align-items: center;
|
||||
& > * {
|
||||
grid-row: 1;
|
||||
}
|
||||
h3,
|
||||
h4 {
|
||||
grid-column: 1/-1;
|
||||
justify-self: center;
|
||||
align-self: center;
|
||||
}
|
||||
&__close {
|
||||
grid-column: 1;
|
||||
margin-left: -1rem;
|
||||
justify-self: flex-start;
|
||||
}
|
||||
}
|
||||
#loading {
|
||||
position: fixed;
|
||||
display: grid;
|
||||
@ -394,6 +437,10 @@ header {
|
||||
sm-input {
|
||||
width: 100%;
|
||||
}
|
||||
button {
|
||||
margin-bottom: auto;
|
||||
height: 3.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
#verification_section {
|
||||
@ -429,6 +476,32 @@ header {
|
||||
min-width: 5rem;
|
||||
}
|
||||
}
|
||||
#approved_kyc_addresses {
|
||||
}
|
||||
.revoke-card {
|
||||
list-style: none;
|
||||
label {
|
||||
gap: 1rem;
|
||||
padding: 0.8rem 0;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
input {
|
||||
accent-color: var(--accent-color);
|
||||
height: 1.3em;
|
||||
width: 1.3em;
|
||||
}
|
||||
span:last-of-type {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 40rem) {
|
||||
sm-popup {
|
||||
--width: 24rem;
|
||||
}
|
||||
}
|
||||
.error {
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
127
index.html
127
index.html
@ -19,7 +19,7 @@
|
||||
<h4>Loading RanchiMall KYC blockchain verification</h4>
|
||||
</div>
|
||||
<article>
|
||||
<header class="flex space-between">
|
||||
<header class="flex gap-1 align-center">
|
||||
<h1 class="flex align-items-center" style="font-size: 1rem;">
|
||||
<svg class="icon" style="margin-right:0.3rem" viewBox="0 0 96 108" xml:space="preserve">
|
||||
<path d="M90.2,102.5c-2.4-8.2-9.9-14.5-27.4-23.1c-7.1-3.5-11.8-6.2-14-8.3c-1.7-1.6-3.5-4-4.2-5.5c-0.7-1.7-0.7-5.5,0-7.5
|
||||
@ -47,6 +47,7 @@
|
||||
</svg>
|
||||
RanchiMall
|
||||
</h1>
|
||||
<a href="/kyc/manage.html" class="button button--colored margin-left-auto">For officials</a>
|
||||
<theme-toggle></theme-toggle>
|
||||
</header>
|
||||
<section id="verification_section" class="grid gap-1-5">
|
||||
@ -75,39 +76,6 @@
|
||||
<script src="scripts/chainkyc.js"></script>
|
||||
<script>
|
||||
const { html, render: renderElem } = uhtml;
|
||||
function getFormattedTime(timestamp, format) {
|
||||
try {
|
||||
if (String(timestamp).length < 13)
|
||||
timestamp *= 1000
|
||||
let [day, month, date, year] = new Date(timestamp).toString().split(' '),
|
||||
minutes = new Date(timestamp).getMinutes(),
|
||||
hours = new Date(timestamp).getHours(),
|
||||
currentTime = new Date().toString().split(' ')
|
||||
|
||||
minutes = minutes < 10 ? `0${minutes}` : minutes
|
||||
let finalHours = ``;
|
||||
if (hours > 12)
|
||||
finalHours = `${hours - 12}:${minutes}`
|
||||
else if (hours === 0)
|
||||
finalHours = `12:${minutes}`
|
||||
else
|
||||
finalHours = `${hours}:${minutes}`
|
||||
|
||||
finalHours = hours >= 12 ? `${finalHours} PM` : `${finalHours} AM`
|
||||
switch (format) {
|
||||
case 'date-only':
|
||||
return `${month} ${date}, ${year}`;
|
||||
break;
|
||||
case 'time-only':
|
||||
return finalHours;
|
||||
default:
|
||||
return `${month} ${date}, ${year} at ${finalHours}`;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
router.addRoute('', async state => {
|
||||
@ -116,45 +84,6 @@
|
||||
router.addRoute('verify', async state => {
|
||||
verify(state.params.address)
|
||||
})
|
||||
floGlobals.approvedKyc = {};
|
||||
function getApprovedKycs() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const aggregatorTxs = Object.keys(floGlobals.approvedKycAggregators).map(aggregator => {
|
||||
return floBlockchainAPI.readAllTxs(aggregator);
|
||||
});
|
||||
Promise.all(aggregatorTxs).then(aggregatorData => {
|
||||
aggregatorData = aggregatorData.flat(1)
|
||||
.filter(tx => tx.vin[0].addr in floGlobals.approvedKycAggregators && tx.floData.startsWith('KYC'))
|
||||
.sort((a, b) => a.time - b.time);
|
||||
for (const tx of aggregatorData) {
|
||||
const { floData, time, vin, vout } = tx;
|
||||
const [service, operationType, operationData, validity] = floData.split('|');
|
||||
switch (operationType) {
|
||||
case 'APPROVE_KYC':
|
||||
operationData.split(',').forEach(address => {
|
||||
floGlobals.approvedKyc[address] = {
|
||||
validFrom: time * 1000,
|
||||
validTo: validity || Date.now() + 10000000,
|
||||
verifiedBy: vin[0].addr
|
||||
};
|
||||
});
|
||||
break;
|
||||
case 'REVOKE_KYC':
|
||||
operationData.split(',').forEach(address => {
|
||||
floGlobals.approvedKyc[address].validTo = time * 1000;
|
||||
floGlobals.approvedKyc[address].revokedBy = vin[0].addr;
|
||||
});
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
}).catch(e => {
|
||||
reject(e);
|
||||
})
|
||||
})
|
||||
}
|
||||
function verify(address) {
|
||||
if (address) {
|
||||
if (getRef('address_verify').value.trim() !== address)
|
||||
@ -174,27 +103,45 @@
|
||||
if (address === '') return renderElem(getRef('verification_result'), html`Please enter an address`);
|
||||
getRef('verification_result').classList.remove('hidden');
|
||||
if (floGlobals.approvedKyc[address]) {
|
||||
const { validFrom, validTo, verifiedBy, revokedBy } = floGlobals.approvedKyc[address];
|
||||
const { validFrom, validTo, issuedBy, revokedBy } = floGlobals.approvedKyc[address];
|
||||
const validFromFormatted = getFormattedTime(validFrom, 'date-only');
|
||||
const validToFormatted = getFormattedTime(validTo, 'date-only');
|
||||
const floID = floCrypto.toFloID(address)
|
||||
const btcID = btcOperator.convert.legacy2bech(floID)
|
||||
if (validFrom <= Date.now() && Date.now() <= validTo) {
|
||||
getRef('verification_result').dataset.status = 'valid';
|
||||
renderElem(getRef('verification_result'), html`
|
||||
<div class="grid justify-items-center text-center gap-0-5" style="margin: 2rem">
|
||||
<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"><g><rect fill="none" height="24" width="24"/></g><g><path d="M23,12l-2.44-2.79l0.34-3.69l-3.61-0.82L15.4,1.5L12,2.96L8.6,1.5L6.71,4.69L3.1,5.5L3.44,9.2L1,12l2.44,2.79l-0.34,3.7 l3.61,0.82L8.6,22.5l3.4-1.47l3.4,1.46l1.89-3.19l3.61-0.82l-0.34-3.69L23,12z M10.09,16.72l-3.8-3.81l1.48-1.48l2.32,2.33 l5.85-5.87l1.48,1.48L10.09,16.72z"/></g></svg>
|
||||
<h4>Verified KYC</h4>
|
||||
<h4>KYC successfully verified</h4>
|
||||
</div>
|
||||
<div class="info flex align-center gap-0-5 flex-wrap">
|
||||
<span>Verified on</span>
|
||||
<span>${getFormattedTime(Date.now(), 'date-only')}</span>
|
||||
<div class="info flex gap-0-5 flex-wrap">
|
||||
<span>Address</span>
|
||||
<div class="grid gap-0-5">
|
||||
<span>
|
||||
BTC:
|
||||
<sm-copy value=${btcID} clip-text></sm-copy>
|
||||
</span>
|
||||
<span>
|
||||
FLO:
|
||||
<sm-copy value=${floID} clip-text></sm-copy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info flex gap-0-5 flex-wrap">
|
||||
<span>Issued by</span>
|
||||
<div class="grid">
|
||||
<h4>${floGlobals.approvedKycAggregators[issuedBy]}</h4>
|
||||
<sm-copy value=${issuedBy} clip-text></sm-copy>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info flex align-center gap-0-5 flex-wrap">
|
||||
<span>Issued on</span>
|
||||
<span>${validFromFormatted}</span>
|
||||
</div>
|
||||
<div class="info flex align-center gap-0-5 flex-wrap">
|
||||
<span>Issued by</span>
|
||||
<sm-copy value=${verifiedBy} clip-text></sm-copy>
|
||||
<span>Verified on</span>
|
||||
<span>${getFormattedTime(Date.now(), 'date-only')}</span>
|
||||
</div>
|
||||
`);
|
||||
} else {
|
||||
@ -203,21 +150,27 @@
|
||||
<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"><g><path d="M0,0h24v24H0V0z" fill="none"/></g><g><path d="M12,2L4,5v6.09c0,5.05,3.41,9.76,8,10.91c4.59-1.15,8-5.86,8-10.91V5L12,2z M15.5,14.09l-1.41,1.41L12,13.42L9.91,15.5 L8.5,14.09L10.59,12L8.5,9.91L9.91,8.5L12,10.59l2.09-2.09l1.41,1.41L13.42,12L15.5,14.09z"/></g></svg>
|
||||
<h4>KYC verification expired</h4>
|
||||
</div>
|
||||
<div class="info flex gap-0-5 flex-wrap">
|
||||
<span>Issued by</span>
|
||||
<div class="grid">
|
||||
<h4>${floGlobals.approvedKycAggregators[issuedBy]}</h4>
|
||||
<sm-copy value=${issuedBy} clip-text></sm-copy>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info flex align-center gap-0-5 flex-wrap">
|
||||
<span>Issued on</span>
|
||||
<span>${validFromFormatted}</span>
|
||||
</div>
|
||||
<div class="info flex align-center gap-0-5 flex-wrap">
|
||||
<span>Issued by</span>
|
||||
<sm-copy value=${verifiedBy} clip-text></sm-copy>
|
||||
</div>
|
||||
<div class="info flex align-center gap-0-5 flex-wrap">
|
||||
<span>Expired on</span>
|
||||
<span>${validToFormatted}</span>
|
||||
</div>
|
||||
<div class="info flex align-center gap-0-5 flex-wrap">
|
||||
<div class="info flex gap-0-5 flex-wrap">
|
||||
<span>Revoked by</span>
|
||||
<sm-copy value=${revokedBy} clip-text></sm-copy>
|
||||
<div class="grid">
|
||||
<h4>${floGlobals.approvedKycAggregators[revokedBy]}</h4>
|
||||
<sm-copy value=${revokedBy} clip-text></sm-copy>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
getRef('verification_result').dataset.status = 'invalid';
|
||||
@ -226,7 +179,7 @@
|
||||
renderElem(getRef('verification_result'), html`
|
||||
<div class="grid justify-items-center text-center gap-0-5" style="margin: 2rem">
|
||||
<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 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"/></svg>
|
||||
<h4>KYC not found</h4>
|
||||
<h4>KYC data not found</h4>
|
||||
</div>
|
||||
`);
|
||||
getRef('verification_result').dataset.status = 'invalid';
|
||||
|
||||
526
manage.html
526
manage.html
@ -15,88 +15,147 @@
|
||||
|
||||
<body>
|
||||
<sm-notifications id="notification_drawer"></sm-notifications>
|
||||
<sm-popup id="confirmation_popup">
|
||||
<h4 id="confirm_title"></h4>
|
||||
<p id="confirm_message"></p>
|
||||
<div class="flex align-center gap-0-5 margin-left-auto">
|
||||
<button class="button cancel-button">Cancel</button>
|
||||
<button class="button button--primary confirm-button">OK</button>
|
||||
</div>
|
||||
</sm-popup>
|
||||
<div id="loading">
|
||||
<sm-spinner></sm-spinner>
|
||||
<h4>Loading</h4>
|
||||
</div>
|
||||
<article>
|
||||
<header class="flex space-between">
|
||||
<h1 class="flex align-items-center" style="font-size: 1rem;">
|
||||
<svg class="icon" style="margin-right:0.3rem" viewBox="0 0 96 108" xml:space="preserve">
|
||||
<path d="M90.2,102.5c-2.4-8.2-9.9-14.5-27.4-23.1c-7.1-3.5-11.8-6.2-14-8.3c-1.7-1.6-3.5-4-4.2-5.5c-0.7-1.7-0.7-5.5,0-7.5
|
||||
c1.3-3.6,2.6-5.2,12.9-15.1c6.2-5.9,9.3-10.3,11.1-15.5c0.7-2.1,0.8-7.6,0.2-9.4C66.5,12,61.7,6.7,53.7,1.6c-3-1.9-4.3-2.1-4.3-0.8
|
||||
c0,0.3-0.5,1.4-1,2.4l-1,1.8l-2.8-1.9c-1.5-1.1-3.4-2.2-4.1-2.6c-1.3-0.7-2.4-0.6-2.4,0.2c0,0.3-1.4,3.4-2,4.4
|
||||
c0,0.1-0.4-0.1-0.9-0.4c-6.1-4.4-8.7-5.5-8.7-3.9c0,0.7-1.8,4.2-4,7.9C16,19.5,9.4,24.9,2.6,24.9c-3,0-2.9-0.1-2,3.4
|
||||
c0.7,2.8,1.1,3.1,3.6,2.3c2.3-0.7,3.9-1.5,5.8-2.9c0.8-0.6,1.5-0.9,1.6-0.9c0.1,0.1,0.5,1,0.7,2.1s0.7,2,0.9,2.1
|
||||
c0.8,0.3,5.1-1.3,7.5-2.9l2.3-1.5l0.5,1.8c0.6,2.4,1,2.7,3.3,2.1c3.9-1,7.7-3.7,11.5-8.2l2-2.4l-0.2,2.1c-0.6,5.4-4.3,11.4-11.3,18
|
||||
c-1.8,1.7-4.7,4.5-6.5,6.2c-10.7,10.2-10,18.6,2,26.5c2.7,1.8,10.3,5.8,15.3,8c0.9,0.4,3.3,1.7,5.3,2.9c11,6.5,16.4,13.1,16.4,19.7
|
||||
c0,1.3,0.1,2.4,0.2,2.6l0,0c0.3,0.3,0.1,0.3,3-0.5c1.4-0.4,2.6-0.9,2.8-1.1c0.4-0.6-0.6-3.7-1.8-6.1c-1.3-2.5-5.6-7-8.9-9.4
|
||||
c-3.8-2.8-9.3-5.9-17-9.7c-8.5-4.2-11.8-6.2-14.7-9.1c-2.6-2.6-3.9-5.3-3.9-8.2c0-4.6,2.3-8.6,8.3-14.1c9.4-8.7,13-13,15.5-18.8
|
||||
c1.3-3,1.4-3.4,1.4-6.7c0-3.1-0.1-3.8-1.1-6l-1.1-2.4l1-1.6c0.5-0.9,1.2-2.1,1.5-2.6l0.5-1l1.5,2.1c1.8,2.6,3.2,6.8,3.2,9.3
|
||||
c0,1.7-0.6,4.7-1.4,6.4c-0.2,0.4-0.4,1-0.5,1.3c-0.1,0.3-1.1,2-2.2,3.7c-2,3-5.2,6.4-13.4,14.2c-5.7,5.4-7.6,8.6-7.8,13.1
|
||||
c-0.2,3.7,0.7,5.9,3.7,9.2c3.2,3.4,6.9,5.8,17.4,11c12.1,6,17.3,9.6,21.3,14.5c2.5,3.2,3.7,5.8,3.9,9.3c0.1,1.6,0.3,3,0.5,3
|
||||
c0.1,0.1,0.8,0,1.4-0.2s1.9-0.5,2.7-0.7l1.5-0.4l-0.2-1.5c-0.7-5.1-5.4-10.8-13.1-16c-4.4-2.9-5.8-3.7-17.3-9.4
|
||||
c-5.7-2.8-9.2-5.1-11.8-7.6c-4.3-4.2-5.1-8.8-2.7-13.9c1.4-2.8,2.7-4.4,12.5-13.8c8-7.7,11.4-13.7,11.4-20.1c0-5.1-2.3-9.9-6.9-14.3
|
||||
c-1.1-1-2-2-2.1-2.2c-0.2-0.4,1.5-3.9,1.9-3.9c1.2,0,7.8,6.3,9.7,9.2c2,3.3,2.5,5,2.5,8.9c0,3.9-0.6,5.9-2.9,9.8
|
||||
c-2.4,4.1-4.2,6-14.2,15.5c-3.4,3.2-5.7,6.1-6.9,8.7c-0.9,2-1.1,2.7-1.1,5.1c0,2.3,0.2,3.2,1,4.9c1.9,4,7.4,8.5,15.4,12.4
|
||||
c12.5,6.1,15.1,7.6,19.4,10.7c7.2,5.3,10.6,10.5,10.6,16c0,1.3,0.1,2.4,0.3,2.5c0.4,0.3,4.8-0.8,5.5-1.3
|
||||
C90.7,104.4,90.7,104.3,90.2,102.5z M20.3,23.3L20.3,23.3c-2,1-3.3,1.4-4.8,1.5L13.3,25l2.3-2.8c3.7-4.5,6.4-8.9,10-16
|
||||
c0.9-1.8,1.8-3.5,2-3.6c0.4-0.4,2.6,1.1,5.1,3.4l2.1,1.9l-1.9,2.8C28.2,17.5,24.5,21.2,20.3,23.3z M39.3,17.4
|
||||
c-1.2,1.7-6.5,5.7-8.6,6.5v0c-1.1,0.4-2.8,0.8-3.9,0.9L24.9,25l2.1-2.6c2.5-3.1,5.1-7,7-10.4c0.7-1.4,1.4-2.5,1.5-2.6
|
||||
c0.3-0.4,1.7,1.4,3,4.1l1.5,3L39.3,17.4z M44.6,10c-0.7,1.2-1.4,2.1-1.5,2.1c-0.1,0-1.5-1.4-3-3l-2.8-3l0.6-1.5
|
||||
c1.1-2.6,1.3-2.7,3.4-1c1.9,1.5,4.5,3.8,4.5,4.1C45.8,7.8,45.3,8.9,44.6,10z"></path>
|
||||
</svg>
|
||||
RanchiMall
|
||||
</h1>
|
||||
<a href="/kyc">
|
||||
<h1 class="flex align-items-center" style="font-size: 1rem;">
|
||||
<svg class="icon" style="margin-right:0.3rem" viewBox="0 0 96 108" xml:space="preserve">
|
||||
<path d="M90.2,102.5c-2.4-8.2-9.9-14.5-27.4-23.1c-7.1-3.5-11.8-6.2-14-8.3c-1.7-1.6-3.5-4-4.2-5.5c-0.7-1.7-0.7-5.5,0-7.5
|
||||
c1.3-3.6,2.6-5.2,12.9-15.1c6.2-5.9,9.3-10.3,11.1-15.5c0.7-2.1,0.8-7.6,0.2-9.4C66.5,12,61.7,6.7,53.7,1.6c-3-1.9-4.3-2.1-4.3-0.8
|
||||
c0,0.3-0.5,1.4-1,2.4l-1,1.8l-2.8-1.9c-1.5-1.1-3.4-2.2-4.1-2.6c-1.3-0.7-2.4-0.6-2.4,0.2c0,0.3-1.4,3.4-2,4.4
|
||||
c0,0.1-0.4-0.1-0.9-0.4c-6.1-4.4-8.7-5.5-8.7-3.9c0,0.7-1.8,4.2-4,7.9C16,19.5,9.4,24.9,2.6,24.9c-3,0-2.9-0.1-2,3.4
|
||||
c0.7,2.8,1.1,3.1,3.6,2.3c2.3-0.7,3.9-1.5,5.8-2.9c0.8-0.6,1.5-0.9,1.6-0.9c0.1,0.1,0.5,1,0.7,2.1s0.7,2,0.9,2.1
|
||||
c0.8,0.3,5.1-1.3,7.5-2.9l2.3-1.5l0.5,1.8c0.6,2.4,1,2.7,3.3,2.1c3.9-1,7.7-3.7,11.5-8.2l2-2.4l-0.2,2.1c-0.6,5.4-4.3,11.4-11.3,18
|
||||
c-1.8,1.7-4.7,4.5-6.5,6.2c-10.7,10.2-10,18.6,2,26.5c2.7,1.8,10.3,5.8,15.3,8c0.9,0.4,3.3,1.7,5.3,2.9c11,6.5,16.4,13.1,16.4,19.7
|
||||
c0,1.3,0.1,2.4,0.2,2.6l0,0c0.3,0.3,0.1,0.3,3-0.5c1.4-0.4,2.6-0.9,2.8-1.1c0.4-0.6-0.6-3.7-1.8-6.1c-1.3-2.5-5.6-7-8.9-9.4
|
||||
c-3.8-2.8-9.3-5.9-17-9.7c-8.5-4.2-11.8-6.2-14.7-9.1c-2.6-2.6-3.9-5.3-3.9-8.2c0-4.6,2.3-8.6,8.3-14.1c9.4-8.7,13-13,15.5-18.8
|
||||
c1.3-3,1.4-3.4,1.4-6.7c0-3.1-0.1-3.8-1.1-6l-1.1-2.4l1-1.6c0.5-0.9,1.2-2.1,1.5-2.6l0.5-1l1.5,2.1c1.8,2.6,3.2,6.8,3.2,9.3
|
||||
c0,1.7-0.6,4.7-1.4,6.4c-0.2,0.4-0.4,1-0.5,1.3c-0.1,0.3-1.1,2-2.2,3.7c-2,3-5.2,6.4-13.4,14.2c-5.7,5.4-7.6,8.6-7.8,13.1
|
||||
c-0.2,3.7,0.7,5.9,3.7,9.2c3.2,3.4,6.9,5.8,17.4,11c12.1,6,17.3,9.6,21.3,14.5c2.5,3.2,3.7,5.8,3.9,9.3c0.1,1.6,0.3,3,0.5,3
|
||||
c0.1,0.1,0.8,0,1.4-0.2s1.9-0.5,2.7-0.7l1.5-0.4l-0.2-1.5c-0.7-5.1-5.4-10.8-13.1-16c-4.4-2.9-5.8-3.7-17.3-9.4
|
||||
c-5.7-2.8-9.2-5.1-11.8-7.6c-4.3-4.2-5.1-8.8-2.7-13.9c1.4-2.8,2.7-4.4,12.5-13.8c8-7.7,11.4-13.7,11.4-20.1c0-5.1-2.3-9.9-6.9-14.3
|
||||
c-1.1-1-2-2-2.1-2.2c-0.2-0.4,1.5-3.9,1.9-3.9c1.2,0,7.8,6.3,9.7,9.2c2,3.3,2.5,5,2.5,8.9c0,3.9-0.6,5.9-2.9,9.8
|
||||
c-2.4,4.1-4.2,6-14.2,15.5c-3.4,3.2-5.7,6.1-6.9,8.7c-0.9,2-1.1,2.7-1.1,5.1c0,2.3,0.2,3.2,1,4.9c1.9,4,7.4,8.5,15.4,12.4
|
||||
c12.5,6.1,15.1,7.6,19.4,10.7c7.2,5.3,10.6,10.5,10.6,16c0,1.3,0.1,2.4,0.3,2.5c0.4,0.3,4.8-0.8,5.5-1.3
|
||||
C90.7,104.4,90.7,104.3,90.2,102.5z M20.3,23.3L20.3,23.3c-2,1-3.3,1.4-4.8,1.5L13.3,25l2.3-2.8c3.7-4.5,6.4-8.9,10-16
|
||||
c0.9-1.8,1.8-3.5,2-3.6c0.4-0.4,2.6,1.1,5.1,3.4l2.1,1.9l-1.9,2.8C28.2,17.5,24.5,21.2,20.3,23.3z M39.3,17.4
|
||||
c-1.2,1.7-6.5,5.7-8.6,6.5v0c-1.1,0.4-2.8,0.8-3.9,0.9L24.9,25l2.1-2.6c2.5-3.1,5.1-7,7-10.4c0.7-1.4,1.4-2.5,1.5-2.6
|
||||
c0.3-0.4,1.7,1.4,3,4.1l1.5,3L39.3,17.4z M44.6,10c-0.7,1.2-1.4,2.1-1.5,2.1c-0.1,0-1.5-1.4-3-3l-2.8-3l0.6-1.5
|
||||
c1.1-2.6,1.3-2.7,3.4-1c1.9,1.5,4.5,3.8,4.5,4.1C45.8,7.8,45.3,8.9,44.6,10z"></path>
|
||||
</svg>
|
||||
RanchiMall
|
||||
</h1>
|
||||
</a>
|
||||
<theme-toggle></theme-toggle>
|
||||
</header>
|
||||
<div class="flex justify-center">
|
||||
<sm-chips id="manage_type" class="margin-bottom-1"
|
||||
style="padding: 0.5rem; background-color: rgba(var(--foreground-color),1); border-radius: 0.8rem;">
|
||||
<sm-chip value="aggregator" selected>Aggregator</sm-chip>
|
||||
<sm-chip value="admin">Admin</sm-chip>
|
||||
</sm-chips>
|
||||
</div>
|
||||
<section id="kyc_section" class="grid gap-1-5">
|
||||
<div class="flex align-center space-between gap-1">
|
||||
<h2>
|
||||
KYC Management
|
||||
<div class="flex flex-wrap align-center space-between gap-1">
|
||||
<h2 id="manage_title">
|
||||
Manage KYC
|
||||
</h2>
|
||||
<sm-chips id="kyc_type">
|
||||
<sm-chip value="approve" selected>Approve</sm-chip>
|
||||
<sm-chip value="revoke">Revoke</sm-chip>
|
||||
</sm-chips>
|
||||
</div>
|
||||
<sm-form id="kyc_form">
|
||||
<div class="grid gap-0-5">
|
||||
<h4>Aggregator credentials</h4>
|
||||
<sm-input id="aggregator_private_key" placeholder="Private key" error-text="Invalid private key"
|
||||
type="password" required animate></sm-input>
|
||||
<p id="aggregator_balance" class="hidden"></p>
|
||||
</div>
|
||||
<div class="grid gap-0-5">
|
||||
<h4 id="address_manage_title">KYCs to be approved</h4>
|
||||
<p class="margin-bottom-1">
|
||||
Enter the addresses of the KYCs you want to approve or revoke. You can add multiple addresses by
|
||||
clicking the "Add address" button.
|
||||
<div class="grid gap-1">
|
||||
<h4 id="credential_title">Aggregator credentials</h4>
|
||||
<sm-input id="approver_private_key" placeholder="Private key" error-text="Invalid private key"
|
||||
type="password" required animate></sm-input>
|
||||
<p id="aggregator_balance" class="hidden"></p>
|
||||
</div>
|
||||
<div id="kyc_mode_wrapper">
|
||||
<sm-form>
|
||||
<div class="grid gap-0-5">
|
||||
<h4 id="approval_title">KYCs to be approved</h4>
|
||||
<p id="approval_description" class="margin-bottom-1">
|
||||
Enter the addresses of the KYCs you want to approve. You can add multiple addresses by
|
||||
clicking the "Add address" button.
|
||||
</p>
|
||||
<ul id="kyc_addresses_container" class="grid gap-1"></ul>
|
||||
<button id="add_address" class="button margin-right-auto" onclick="addAddressInput()">Add
|
||||
address</button>
|
||||
</div>
|
||||
<div class="multi-state-button">
|
||||
<button id="submit_kyc" class="button button--primary" onclick="approveAddresses()"
|
||||
type="submit" disabled>Approve</button>
|
||||
</div>
|
||||
</sm-form>
|
||||
<div class="grid gap-1 hidden">
|
||||
<div class="grid gap-0-5">
|
||||
<h4 id="revoke_title">Approved KYC addresses</h4>
|
||||
<p id="revoke_description">
|
||||
To revoke a KYC, select one or multiple KYCs and click on the 'Revoke selected' button.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid gap-0-5"
|
||||
style="position: sticky; top: 0; z-index: 2; background-color: rgba(var(--foreground-color),1);">
|
||||
<sm-input id="search_approved" type="search" placeholder="Search">
|
||||
<svg class="icon" slot="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="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
|
||||
</svg>
|
||||
</sm-input>
|
||||
<div id="selected_wrapper" class="flex gap-0-5 align-center hidden">
|
||||
<button class="button icon-only" title="Clear selection" onclick="clearSelection()">
|
||||
<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="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
|
||||
</svg>
|
||||
</button>
|
||||
<div id="selected_addresses"></div>
|
||||
<div class="multi-state-button margin-left-auto">
|
||||
<button id="revoke_kyc_button" class="button button--primary"
|
||||
onclick="revokeAddresses()">Revoke selected</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul id="approved_addresses" class="grid gap-0-3"></ul>
|
||||
<p id="no_results" class="hidden">
|
||||
No addresses found.
|
||||
</p>
|
||||
<ul id="kyc_addresses_container" class="grid gap-0-3"></ul>
|
||||
<button id="add_address" class="button margin-right-auto" onclick="addAddressInput()">Add
|
||||
address</button>
|
||||
</div>
|
||||
<div class="multi-state-button">
|
||||
<button id="submit_kyc" class="button button--primary" onclick="submitAddresses()"
|
||||
type="submit">Approve</button>
|
||||
</div>
|
||||
</sm-form>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
<template id="key_address_template">
|
||||
<template id="kyc_address_template">
|
||||
<li class="address-input flex align-center gap-0-5">
|
||||
<sm-input class="kyc-address" placeholder="FLO/BTC address" error-text="Invalid address" required
|
||||
animate></sm-input>
|
||||
<button class="remove-address button--small" onclick="removeAddressInput(this)">
|
||||
<svg class="icon margin-right-0-3" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
||||
width="24px" fill="#000000">
|
||||
<div class="grid gap-0-3 flex-1">
|
||||
<sm-input class="kyc-address" placeholder="FLO/BTC address" error-text="Invalid address" required
|
||||
animate></sm-input>
|
||||
<sm-input class="aggregator-label" placeholder="Label" required animate></sm-input>
|
||||
</div>
|
||||
<button class="remove-address button icon-only" onclick="removeAddressInput(this)">
|
||||
<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>
|
||||
<path
|
||||
d="M7 11v2h10v-2H7zm5-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z">
|
||||
</path>
|
||||
</svg>
|
||||
Remove
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
@ -153,30 +212,185 @@
|
||||
if (potentialTarget) potentialTarget.remove();
|
||||
}
|
||||
}
|
||||
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>
|
||||
router.addRoute('', state => {
|
||||
console.log(state)
|
||||
})
|
||||
getRef('kyc_type').addEventListener('change', e => {
|
||||
if (e.target.value === 'approve') {
|
||||
getRef('address_manage_title').textContent = 'KYCs to be approved'
|
||||
getRef('submit_kyc').textContent = 'Approve'
|
||||
const render = {
|
||||
approvedKycAddressCard(address) {
|
||||
const floID = floCrypto.toFloID(address)
|
||||
const btcID = btcOperator.convert.legacy2bech(floID)
|
||||
return html`
|
||||
<li class="revoke-card" data-address=${btcID} data-search-key=${`${btcID}-${floID}`}>
|
||||
<label class="flex align-center">
|
||||
<input type="checkbox" value=${btcID}/>
|
||||
<div class="grid gap-0-3">
|
||||
<span class="wrap-around">BTC: ${btcID}</span>
|
||||
<span class="wrap-around">FLO: ${floID}</span>
|
||||
</div>
|
||||
</label>
|
||||
</li>
|
||||
`
|
||||
},
|
||||
approvedKycAddresses() {
|
||||
const approvedKycAddresses = Object.keys(floGlobals.approvedKyc)
|
||||
.filter(address => !floGlobals.approvedKyc[address].revokedBy)
|
||||
.map(address => render.approvedKycAddressCard(address))
|
||||
renderElem(getRef('approved_addresses'), html`${approvedKycAddresses}`)
|
||||
},
|
||||
approvedAggregatorCard(address) {
|
||||
const floID = floCrypto.toFloID(address)
|
||||
const btcID = btcOperator.convert.legacy2bech(floID)
|
||||
return html`
|
||||
<li class="revoke-card" data-address=${btcID} data-search-key=${`${floGlobals.approvedKycAggregators[address]}-${btcID}-${floID}`}>
|
||||
<label class="flex align-center">
|
||||
<input type="checkbox" value=${btcID}/>
|
||||
<div class="grid gap-0-5">
|
||||
<h4>${floGlobals.approvedKycAggregators[address]}</h4>
|
||||
<div class="grid gap-0-3">
|
||||
<span class="wrap-around">BTC: ${btcID}</span>
|
||||
<span class="wrap-around">FLO: ${floID}</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</li>
|
||||
`
|
||||
},
|
||||
approvedAggregators() {
|
||||
const approvedAggregators = Object.keys(floGlobals.approvedKycAggregators)
|
||||
.map(address => render.approvedAggregatorCard(address))
|
||||
renderElem(getRef('approved_addresses'), html`${approvedAggregators}`)
|
||||
},
|
||||
ui() {
|
||||
const manageType = getRef('manage_type').value
|
||||
const actionType = getRef('kyc_type').value
|
||||
if (manageType === 'aggregator') {
|
||||
getRef('manage_title').textContent = 'Manage KYC'
|
||||
getRef('credential_title').textContent = 'Aggregator credentials'
|
||||
getRef('approval_title').textContent = 'KYCs to be approved'
|
||||
getRef('approval_description').textContent = `
|
||||
Enter the addresses of the KYCs you want to approve. You can add multiple addresses by clicking the "Add address" button.
|
||||
`
|
||||
getRef('revoke_title').textContent = 'Approved KYC addresses'
|
||||
getRef('revoke_description').textContent = `
|
||||
To revoke a KYC, select one or multiple KYCs and click on the 'Revoke selected' button.
|
||||
`
|
||||
render.approvedKycAddresses()
|
||||
getRef('search_approved').placeholder = 'Search addresses'
|
||||
} else {
|
||||
getRef('manage_title').textContent = 'Manage aggregators'
|
||||
getRef('credential_title').textContent = 'Admin credentials'
|
||||
getRef('approval_title').textContent = 'Aggregators to be approved'
|
||||
getRef('approval_description').textContent = `
|
||||
Enter the addresses of the aggregators you want to approve. You can add multiple addresses by clicking the "Add address" button.
|
||||
`
|
||||
getRef('revoke_title').textContent = 'Approved aggregator addresses'
|
||||
getRef('revoke_description').textContent = `
|
||||
To revoke an aggregator, select one or multiple aggregators and click on the 'Revoke selected' button.
|
||||
`
|
||||
render.approvedAggregators()
|
||||
getRef('search_approved').placeholder = 'Search names or addresses'
|
||||
}
|
||||
if (actionType === 'approve') {
|
||||
showChildElement('kyc_mode_wrapper', 0)
|
||||
} else {
|
||||
showChildElement('kyc_mode_wrapper', 1)
|
||||
}
|
||||
getRef('kyc_addresses_container').innerHTML = '';
|
||||
clearSelection()
|
||||
addAddressInput()
|
||||
}
|
||||
}
|
||||
|
||||
getRef('manage_type').addEventListener('change', render.ui)
|
||||
|
||||
const addressesToRevoke = new Set()
|
||||
getRef('kyc_type').addEventListener('change', render.ui)
|
||||
getRef('search_approved').addEventListener('input', e => {
|
||||
const searchKey = e.target.value.toLowerCase()
|
||||
getRef('approved_addresses').querySelectorAll('li').forEach(li => {
|
||||
if (li.dataset.searchKey.toLowerCase().includes(searchKey)) {
|
||||
li.classList.remove('hidden')
|
||||
} else {
|
||||
li.classList.add('hidden')
|
||||
}
|
||||
})
|
||||
// show message if no results
|
||||
if (getRef('approved_addresses').querySelectorAll('li:not(.hidden)').length === 0) {
|
||||
getRef('no_results').classList.remove('hidden')
|
||||
} else {
|
||||
getRef('address_manage_title').textContent = 'KYCs to be revoked'
|
||||
getRef('submit_kyc').textContent = 'Revoke'
|
||||
getRef('no_results').classList.add('hidden')
|
||||
}
|
||||
})
|
||||
getRef('aggregator_private_key').addEventListener('input', e => {
|
||||
checkBalance(floCrypto.getAddress(e.target.value.trim()))
|
||||
getRef('approved_addresses').addEventListener('change', e => {
|
||||
if (e.target.checked) {
|
||||
const addresses = processAddresses([...addressesToRevoke])
|
||||
const floData = `KYC|REVOKE_KYC|${addresses.join(',')}`
|
||||
if (floData.length > 1040) {
|
||||
return notify('Maximum limit reached for this batch. Revoke selected and continue the process.', 'error')
|
||||
}
|
||||
addressesToRevoke.add(e.target.value)
|
||||
} else {
|
||||
addressesToRevoke.delete(e.target.value)
|
||||
}
|
||||
console.log(addressesToRevoke)
|
||||
if (addressesToRevoke.size) {
|
||||
getRef('selected_addresses').textContent = `${addressesToRevoke.size} selected`
|
||||
getRef('selected_wrapper').classList.remove('hidden')
|
||||
} else {
|
||||
getRef('selected_addresses').textContent = ``
|
||||
getRef('selected_wrapper').classList.add('hidden')
|
||||
}
|
||||
})
|
||||
function clearSelection() {
|
||||
addressesToRevoke.clear()
|
||||
getRef('selected_addresses').textContent = ``
|
||||
getRef('selected_wrapper').classList.add('hidden')
|
||||
getRef('approved_addresses').querySelectorAll('input').forEach(input => input.checked = false)
|
||||
}
|
||||
getRef('approver_private_key').addEventListener('input', e => {
|
||||
checkBalance(floCrypto.getFloID(e.target.value.trim()))
|
||||
})
|
||||
function checkBalance(address) {
|
||||
if (!address)
|
||||
address = floCrypto.getAddress(getRef('aggregator_private_key').value.trim())
|
||||
if (getRef('aggregator_private_key').isValid) {
|
||||
address = floCrypto.getFloID(getRef('approver_private_key').value.trim())
|
||||
if (getRef('approver_private_key').isValid) {
|
||||
floBlockchainAPI.getBalance(address).then(balance => {
|
||||
if (balance < 0.01) {
|
||||
renderElem(getRef('aggregator_balance'), html`Balance: ${balance}FLO. You don't have enough FLO.`);
|
||||
renderElem(getRef('aggregator_balance'), html`Balance: ${balance} FLO. You don't have enough FLO.`);
|
||||
getRef('aggregator_balance').classList.add('error')
|
||||
} else {
|
||||
renderElem(getRef('aggregator_balance'), html`Balance: ${balance} FLO`);
|
||||
@ -193,10 +407,13 @@
|
||||
}
|
||||
}
|
||||
function addAddressInput() {
|
||||
const addressInput = getRef('key_address_template').content.cloneNode(true)
|
||||
const manageType = getRef('manage_type').value
|
||||
const addressInput = getRef('kyc_address_template').content.cloneNode(true)
|
||||
if (!getRef('kyc_addresses_container').firstElementChild) {
|
||||
addressInput.querySelector('.remove-address').remove()
|
||||
}
|
||||
if (manageType === 'aggregator')
|
||||
addressInput.querySelector('.aggregator-label').remove()
|
||||
getRef('kyc_addresses_container').appendChild(addressInput)
|
||||
}
|
||||
function removeAddressInput(button) {
|
||||
@ -211,10 +428,9 @@
|
||||
const removeButton = mutation.target.firstElementChild.querySelector('.remove-address')
|
||||
if (!removeButton) {
|
||||
const newRemoveButton = html.node`
|
||||
<button class="remove-address button--small" onclick="removeAddressInput(this)">
|
||||
<svg class="icon margin-right-0-3" 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> <path d="M7 11v2h10v-2H7zm5-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path> </svg>
|
||||
Remove
|
||||
</button>
|
||||
<button class="remove-address button icon-only" onclick="removeAddressInput(this)">
|
||||
<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> <path d="M7 11v2h10v-2H7zm5-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"> </path> </svg>
|
||||
</button>
|
||||
`
|
||||
mutation.target.firstElementChild.appendChild(newRemoveButton)
|
||||
}
|
||||
@ -231,58 +447,144 @@
|
||||
addressInputObserver.observe(getRef('kyc_addresses_container'), {
|
||||
childList: true
|
||||
})
|
||||
function processAddresses() {
|
||||
const addressInputs = document.querySelectorAll('.kyc-address')
|
||||
const addresses = new Set()
|
||||
addressInputs.forEach(input => {
|
||||
const address = input.value.trim()
|
||||
if (address !== '') {
|
||||
let equivalentBtcAddress = address
|
||||
if (floCrypto.validateFloID(address))
|
||||
equivalentBtcAddress = btcOperator.convert.legacy2bech(address)
|
||||
addresses.add(equivalentBtcAddress)
|
||||
}
|
||||
function processAddresses(addresses = []) {
|
||||
const uniqueAddresses = new Set()
|
||||
addresses.forEach(address => {
|
||||
let equivalentBtcAddress = address
|
||||
if (floCrypto.validateFloID(address))
|
||||
equivalentBtcAddress = btcOperator.convert.legacy2bech(address)
|
||||
uniqueAddresses.add(equivalentBtcAddress)
|
||||
})
|
||||
return [...addresses]
|
||||
return [...uniqueAddresses]
|
||||
}
|
||||
function submitAddresses() {
|
||||
const aggregatorPrivateKey = getRef('aggregator_private_key').value.trim()
|
||||
const aggregatorAddress = floCrypto.getAddress(aggregatorPrivateKey)
|
||||
if (!floGlobals.approvedKycAggregators.hasOwnProperty(aggregatorAddress)) {
|
||||
notify('KYC aggregator address is not approved', 'error')
|
||||
return
|
||||
function approveAddresses() {
|
||||
const manageType = getRef('manage_type').value
|
||||
const approverPrivateKey = getRef('approver_private_key').value.trim()
|
||||
if (!approverPrivateKey) {
|
||||
return notify(`Enter ${manageType} private key`, 'error')
|
||||
}
|
||||
const addresses = processAddresses()
|
||||
const approverAddress = floCrypto.getFloID(approverPrivateKey)
|
||||
if (manageType === 'aggregator') {
|
||||
if (!floGlobals.approvedKycAggregators.hasOwnProperty(approverAddress)) {
|
||||
return notify('KYC aggregator address is not approved', 'error')
|
||||
}
|
||||
} else {
|
||||
if (floGlobals.masterAddress !== approverAddress) {
|
||||
return notify(`Private key doesn't match admin`, 'error')
|
||||
}
|
||||
}
|
||||
const addressInputs = [...document.querySelectorAll('.kyc-address')]
|
||||
.filter(input => input.value.trim() !== '')
|
||||
.map(input => input.value.trim())
|
||||
let addressLabels = []
|
||||
if (manageType === 'admin') {
|
||||
addressLabels = [...document.querySelectorAll('.aggregator-label')]
|
||||
.filter(input => input.value.trim() !== '')
|
||||
.map(input => input.value.trim())
|
||||
}
|
||||
const addresses = processAddresses(addressInputs)
|
||||
if (addresses.length === 0) {
|
||||
notify('No addresses to process', 'error')
|
||||
return
|
||||
return notify('Enter at least one address to approve', 'error')
|
||||
}
|
||||
const kycType = getRef('kyc_type').value
|
||||
const floData = `KYC|${kycType === 'approve' ? 'APPROVE_KYC' : 'REVOKE_KYC'}|${addresses.join(',')}`
|
||||
let floData
|
||||
if (manageType === 'aggregator')
|
||||
floData = `KYC|APPROVE_KYC|${addresses.join('+')}`
|
||||
else {
|
||||
const addressWithLabels = addresses.map((address, index) => {
|
||||
return `${address}:${addressLabels[index]}`
|
||||
})
|
||||
floData = `KYC|APPROVE_AGGREGATOR|${addressWithLabels.join('+')}`
|
||||
}
|
||||
console.log(floData)
|
||||
if (floData.length > 1040) {
|
||||
notify('Too many addresses. Try removing one and resubmitting.', 'error')
|
||||
return
|
||||
return notify('Too many addresses. Try removing one and resubmitting.', 'error')
|
||||
}
|
||||
buttonLoader(getRef('submit_kyc'), true)
|
||||
floBlockchainAPI.writeData(aggregatorAddress, floData, aggregatorPrivateKey, aggregatorAddress).then(txId => {
|
||||
notify(`${kycType === 'approve' ? 'Approval' : 'Revoke'} request submitted. TXID: ${txId}`, 'success')
|
||||
getRef('kyc_addresses_container').innerHTML = '';
|
||||
getRef('kyc_form').reset()
|
||||
addAddressInput()
|
||||
}).catch(e => {
|
||||
notify(e, 'error')
|
||||
}).finally(() => {
|
||||
buttonLoader(getRef('submit_kyc'), false)
|
||||
setTimeout(() => {
|
||||
checkBalance()
|
||||
}, 1000)
|
||||
getConfirmation('Approve entered addresses?', {
|
||||
confirmText: 'Approve',
|
||||
}).then((res) => {
|
||||
if (!res) return
|
||||
buttonLoader(getRef('submit_kyc'), true)
|
||||
floBlockchainAPI.writeData(approverAddress, floData, approverPrivateKey, approverAddress).then(txId => {
|
||||
notify(`Approval request submitted. TXID: ${txId}`, 'success')
|
||||
getRef('kyc_addresses_container').innerHTML = '';
|
||||
getRef('approver_private_key').value = ''
|
||||
addAddressInput()
|
||||
}).catch(e => {
|
||||
notify(e, 'error')
|
||||
}).finally(() => {
|
||||
buttonLoader(getRef('submit_kyc'), false)
|
||||
setTimeout(() => {
|
||||
checkBalance()
|
||||
}, 1000)
|
||||
})
|
||||
})
|
||||
}
|
||||
function revokeAddresses() {
|
||||
const manageType = getRef('manage_type').value
|
||||
const approverPrivateKey = getRef('approver_private_key').value.trim()
|
||||
if (approverPrivateKey === '') {
|
||||
return notify('Please enter KYC aggregator private key', 'error')
|
||||
}
|
||||
const approverAddress = floCrypto.getFloID(approverPrivateKey)
|
||||
if (manageType === 'aggregator') {
|
||||
if (!floGlobals.approvedKycAggregators.hasOwnProperty(approverAddress)) {
|
||||
return notify('KYC aggregator address is not approved', 'error')
|
||||
}
|
||||
} else {
|
||||
if (floGlobals.masterAddress !== approverAddress) {
|
||||
return notify(`Private key doesn't match admin`, 'error')
|
||||
}
|
||||
}
|
||||
const addresses = processAddresses([...addressesToRevoke])
|
||||
if (addresses.length === 0) {
|
||||
return notify('Select at least one address to revoke', 'error')
|
||||
}
|
||||
let floData
|
||||
if (manageType === 'aggregator')
|
||||
floData = `KYC|REVOKE_KYC|${addresses.join('+')}`
|
||||
else
|
||||
floData = `KYC|REVOKE_AGGREGATOR|${addresses.join('+')}`
|
||||
console.log(floData)
|
||||
getConfirmation('Revoke selected addresses?', {
|
||||
confirmText: 'Revoke',
|
||||
}).then((res) => {
|
||||
if (!res) return
|
||||
buttonLoader(getRef('revoke_kyc_button'), true)
|
||||
floBlockchainAPI.writeData(approverAddress, floData, approverPrivateKey, approverAddress).then(txId => {
|
||||
notify(`Revoke request submitted. TXID: ${txId}`, 'success')
|
||||
getRef('approver_private_key').value = ''
|
||||
if (manageType === 'aggregator') {
|
||||
addressesToRevoke.forEach(address => {
|
||||
if (!floGlobals.approvedKyc.hasOwnProperty(address)) return
|
||||
floGlobals.approvedKyc[address].revokedBy = approverAddress
|
||||
floGlobals.approvedKyc[address].validTo = Date.now()
|
||||
})
|
||||
render.approvedKycAddresses()
|
||||
} else {
|
||||
addressesToRevoke.forEach(address => {
|
||||
if (!floGlobals.approvedKycAggregators.hasOwnProperty(address)) return
|
||||
delete floGlobals.approvedKycAggregators[address]
|
||||
})
|
||||
render.approvedAggregators()
|
||||
}
|
||||
addressesToRevoke.clear()
|
||||
clearSelection()
|
||||
}).catch(e => {
|
||||
notify(e, 'error')
|
||||
}).finally(() => {
|
||||
buttonLoader(getRef('revoke_kyc_button'), false)
|
||||
setTimeout(() => {
|
||||
checkBalance()
|
||||
}, 1000)
|
||||
})
|
||||
})
|
||||
}
|
||||
window.onload = async () => {
|
||||
try {
|
||||
await getApprovedAggregators()
|
||||
await getApprovedKycs()
|
||||
router.routeTo(window.location.hash)
|
||||
getRef('aggregator_private_key').customValidation = floCrypto.getPubKeyHex
|
||||
getRef('approver_private_key').customValidation = floCrypto.getPubKeyHex
|
||||
addAddressInput()
|
||||
} catch (e) {
|
||||
notify(e, 'error')
|
||||
|
||||
@ -19,6 +19,84 @@ function getRef(elementId) {
|
||||
}
|
||||
}
|
||||
}
|
||||
let zIndex = 50
|
||||
// function required for popups or modals to appear
|
||||
function openPopup(popupId, pinned) {
|
||||
zIndex++
|
||||
getRef(popupId).setAttribute('style', `z-index: ${zIndex}`)
|
||||
return getRef(popupId).show({ pinned })
|
||||
}
|
||||
|
||||
// hides the popup or modal
|
||||
function closePopup(options = {}) {
|
||||
if (popupStack.peek() === undefined)
|
||||
return;
|
||||
popupStack.peek().popup.hide(options)
|
||||
}
|
||||
// displays a popup for asking permission. Use this instead of JS confirm
|
||||
const getConfirmation = (title, options = {}) => {
|
||||
return new Promise(resolve => {
|
||||
const { message = '', cancelText = 'Cancel', confirmText = 'OK', danger = false } = options
|
||||
getRef('confirm_title').innerText = title;
|
||||
getRef('confirm_message').innerText = message;
|
||||
const cancelButton = getRef('confirmation_popup').querySelector('.cancel-button');
|
||||
const confirmButton = getRef('confirmation_popup').querySelector('.confirm-button')
|
||||
confirmButton.textContent = confirmText
|
||||
cancelButton.textContent = cancelText
|
||||
if (danger)
|
||||
confirmButton.classList.add('button--danger')
|
||||
else
|
||||
confirmButton.classList.remove('button--danger')
|
||||
const { closed } = openPopup('confirmation_popup')
|
||||
confirmButton.onclick = () => {
|
||||
closePopup({ payload: true })
|
||||
}
|
||||
cancelButton.onclick = () => {
|
||||
closePopup()
|
||||
}
|
||||
closed.then((payload) => {
|
||||
confirmButton.onclick = null
|
||||
cancelButton.onclick = null
|
||||
if (payload)
|
||||
resolve(true)
|
||||
else
|
||||
resolve(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
function getFormattedTime(timestamp, format) {
|
||||
try {
|
||||
if (String(timestamp).length < 13)
|
||||
timestamp *= 1000
|
||||
let [day, month, date, year] = new Date(timestamp).toString().split(' '),
|
||||
minutes = new Date(timestamp).getMinutes(),
|
||||
hours = new Date(timestamp).getHours(),
|
||||
currentTime = new Date().toString().split(' ')
|
||||
|
||||
minutes = minutes < 10 ? `0${minutes}` : minutes
|
||||
let finalHours = ``;
|
||||
if (hours > 12)
|
||||
finalHours = `${hours - 12}:${minutes}`
|
||||
else if (hours === 0)
|
||||
finalHours = `12:${minutes}`
|
||||
else
|
||||
finalHours = `${hours}:${minutes}`
|
||||
|
||||
finalHours = hours >= 12 ? `${finalHours} PM` : `${finalHours} AM`
|
||||
switch (format) {
|
||||
case 'date-only':
|
||||
return `${month} ${date}, ${year}`;
|
||||
break;
|
||||
case 'time-only':
|
||||
return finalHours;
|
||||
default:
|
||||
return `${month} ${date}, ${year} at ${finalHours}`;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
class Router {
|
||||
constructor(options = {}) {
|
||||
@ -83,8 +161,8 @@ function loading(show = true) {
|
||||
}
|
||||
}
|
||||
|
||||
floGlobals.approvedKycAggregators = {};
|
||||
function getApprovedAggregators() {
|
||||
floGlobals.approvedKycAggregators = {};
|
||||
return new Promise((resolve, reject) => {
|
||||
floBlockchainAPI.readAllTxs(floGlobals.masterAddress).then(txs => {
|
||||
txs.filter(tx => floCrypto.isSameAddr(tx.vin[0].addr, floGlobals.masterAddress) && tx.floData.startsWith('KYC'))
|
||||
@ -94,16 +172,15 @@ function getApprovedAggregators() {
|
||||
const [service, operationType, operationData, validity] = floData.split('|');
|
||||
switch (operationType) {
|
||||
case 'APPROVE_AGGREGATOR':
|
||||
operationData.split(',').forEach(aggregator => {
|
||||
floGlobals.approvedKycAggregators[floCrypto.toFloID(aggregator)] = {
|
||||
validFrom: time * 1000,
|
||||
validTo: validity || Date.now() + 10000000
|
||||
};
|
||||
operationData.split('+').forEach(aggregator => {
|
||||
const [address, label = ''] = aggregator.split(':');
|
||||
floGlobals.approvedKycAggregators[floCrypto.toFloID(address)] = label;
|
||||
});
|
||||
break;
|
||||
case 'REVOKE_AGGREGATOR':
|
||||
operationData.split(',').forEach(aggregator => {
|
||||
floGlobals.approvedKycAggregators[floCrypto.toFloID(aggregator)].validTo = time * 1000;
|
||||
operationData.split('+').forEach(aggregator => {
|
||||
const [address, label = ''] = aggregator.split(':');
|
||||
delete floGlobals.approvedKycAggregators[floCrypto.toFloID(address)]
|
||||
});
|
||||
break;
|
||||
default:
|
||||
@ -116,4 +193,45 @@ function getApprovedAggregators() {
|
||||
reject(e);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getApprovedKycs() {
|
||||
floGlobals.approvedKyc = {};
|
||||
return new Promise((resolve, reject) => {
|
||||
const aggregatorTxs = Object.keys(floGlobals.approvedKycAggregators).map(aggregator => {
|
||||
return floBlockchainAPI.readAllTxs(aggregator);
|
||||
});
|
||||
Promise.all(aggregatorTxs).then(aggregatorData => {
|
||||
aggregatorData = aggregatorData.flat(1)
|
||||
.filter(tx => tx.vin[0].addr in floGlobals.approvedKycAggregators && tx.floData.startsWith('KYC'))
|
||||
.sort((a, b) => a.time - b.time);
|
||||
for (const tx of aggregatorData) {
|
||||
const { floData, time, vin, vout } = tx;
|
||||
const [service, operationType, operationData, validity] = floData.split('|');
|
||||
switch (operationType) {
|
||||
case 'APPROVE_KYC':
|
||||
operationData.split('+').forEach(address => {
|
||||
floGlobals.approvedKyc[address] = {
|
||||
validFrom: time * 1000,
|
||||
validTo: validity || Date.now() + 10000000,
|
||||
issuedBy: vin[0].addr
|
||||
};
|
||||
});
|
||||
break;
|
||||
case 'REVOKE_KYC':
|
||||
operationData.split('+').forEach(address => {
|
||||
if (!floGlobals.approvedKyc[address]) return
|
||||
floGlobals.approvedKyc[address].validTo = time * 1000;
|
||||
floGlobals.approvedKyc[address].revokedBy = vin[0].addr;
|
||||
});
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
}).catch(e => {
|
||||
reject(e);
|
||||
})
|
||||
})
|
||||
}
|
||||
1
scripts/components.min.js
vendored
1
scripts/components.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
const floGlobals = {
|
||||
blockchain: "FLO",
|
||||
adminID: "FKAEdnPfjXLHSYwrXQu377ugN4tXU7VGdf",
|
||||
application: "TEST_MODE",
|
||||
masterAddress: 'FHDqCJUGdHKHTHdLa8wuQ9NKXNNawfoDJ6'
|
||||
application: "KYC",
|
||||
masterAddress: 'FRSUrLuy5nhz6PCyuxMmo7vg9JmTFX5Goi'
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user