KYC revocation changes
-- listing approved addresses -- revocation is done with selection UI -- added search option
This commit is contained in:
parent
82e2b0993e
commit
80b7d70d86
70
css/main.css
70
css/main.css
@ -367,6 +367,10 @@ sm-chip[selected] {
|
|||||||
--background: var(--accent-color);
|
--background: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sm-input {
|
||||||
|
--border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.multi-state-button {
|
.multi-state-button {
|
||||||
display: grid;
|
display: grid;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -381,6 +385,43 @@ sm-chip[selected] {
|
|||||||
width: 100%;
|
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 {
|
#loading {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -414,6 +455,10 @@ header {
|
|||||||
.address-input sm-input {
|
.address-input sm-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.address-input button {
|
||||||
|
margin-bottom: auto;
|
||||||
|
height: 3.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
#verification_result {
|
#verification_result {
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -444,6 +489,31 @@ header {
|
|||||||
min-width: 5rem;
|
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 {
|
.error {
|
||||||
color: var(--danger-color);
|
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
@ -350,6 +350,9 @@ sm-chip {
|
|||||||
--background: var(--accent-color);
|
--background: var(--accent-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sm-input {
|
||||||
|
--border-radius: 0.5rem;
|
||||||
|
}
|
||||||
.multi-state-button {
|
.multi-state-button {
|
||||||
display: grid;
|
display: grid;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -363,6 +366,41 @@ sm-chip {
|
|||||||
width: 100%;
|
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 {
|
#loading {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -394,6 +432,10 @@ header {
|
|||||||
sm-input {
|
sm-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
button {
|
||||||
|
margin-bottom: auto;
|
||||||
|
height: 3.2rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#verification_section {
|
#verification_section {
|
||||||
@ -429,6 +471,32 @@ header {
|
|||||||
min-width: 5rem;
|
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 {
|
.error {
|
||||||
color: var(--danger-color);
|
color: var(--danger-color);
|
||||||
}
|
}
|
||||||
|
|||||||
39
index.html
39
index.html
@ -116,45 +116,6 @@
|
|||||||
router.addRoute('verify', async state => {
|
router.addRoute('verify', async state => {
|
||||||
verify(state.params.address)
|
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) {
|
function verify(address) {
|
||||||
if (address) {
|
if (address) {
|
||||||
if (getRef('address_verify').value.trim() !== address)
|
if (getRef('address_verify').value.trim() !== address)
|
||||||
|
|||||||
297
manage.html
297
manage.html
@ -15,6 +15,14 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<sm-notifications id="notification_drawer"></sm-notifications>
|
<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">
|
<div id="loading">
|
||||||
<sm-spinner></sm-spinner>
|
<sm-spinner></sm-spinner>
|
||||||
<h4>Loading</h4>
|
<h4>Loading</h4>
|
||||||
@ -60,28 +68,65 @@
|
|||||||
<sm-chip value="revoke">Revoke</sm-chip>
|
<sm-chip value="revoke">Revoke</sm-chip>
|
||||||
</sm-chips>
|
</sm-chips>
|
||||||
</div>
|
</div>
|
||||||
<sm-form id="kyc_form">
|
<div class="grid gap-0-5">
|
||||||
<div class="grid gap-0-5">
|
<h4>Aggregator credentials</h4>
|
||||||
<h4>Aggregator credentials</h4>
|
<sm-input id="aggregator_private_key" placeholder="Private key" error-text="Invalid private key"
|
||||||
<sm-input id="aggregator_private_key" placeholder="Private key" error-text="Invalid private key"
|
type="password" required animate></sm-input>
|
||||||
type="password" required animate></sm-input>
|
<p id="aggregator_balance" class="hidden"></p>
|
||||||
<p id="aggregator_balance" class="hidden"></p>
|
</div>
|
||||||
|
<div id="kyc_mode_wrapper">
|
||||||
|
<sm-form>
|
||||||
|
<div class="grid gap-0-5">
|
||||||
|
<h4>KYCs to be approved</h4>
|
||||||
|
<p 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-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="approveAddresses()"
|
||||||
|
type="submit" disabled>Approve</button>
|
||||||
|
</div>
|
||||||
|
</sm-form>
|
||||||
|
<div class="grid gap-1 hidden">
|
||||||
|
<div class="grid gap-0-5">
|
||||||
|
<h4>Approved KYC addresses</h4>
|
||||||
|
<p>
|
||||||
|
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 address">
|
||||||
|
<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 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">
|
||||||
|
<button id="revoke_kyc_button" class="button button--primary margin-left-auto"
|
||||||
|
onclick="revokeAddresses()">Revoke selected</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul id="approved_kyc_addresses" class="grid gap-0-3"></ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid gap-0-5">
|
</div>
|
||||||
<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.
|
|
||||||
</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>
|
|
||||||
</section>
|
</section>
|
||||||
</article>
|
</article>
|
||||||
<template id="key_address_template">
|
<template id="key_address_template">
|
||||||
@ -153,20 +198,110 @@
|
|||||||
if (potentialTarget) potentialTarget.remove();
|
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>
|
||||||
<script>
|
<script>
|
||||||
router.addRoute('', state => {
|
router.addRoute('', state => {
|
||||||
console.log(state)
|
console.log(state)
|
||||||
})
|
})
|
||||||
|
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>BTC: ${btcID}</span>
|
||||||
|
<span>FLO: ${floID}</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
approvedKycs() {
|
||||||
|
const approvedKycs = Object.keys(floGlobals.approvedKyc)
|
||||||
|
.filter(address => !floGlobals.approvedKyc[address].revokedBy)
|
||||||
|
.map(address => render.approvedKycAddressCard(address))
|
||||||
|
renderElem(getRef('approved_kyc_addresses'), html`${approvedKycs}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const addressesToRevoke = new Set()
|
||||||
getRef('kyc_type').addEventListener('change', e => {
|
getRef('kyc_type').addEventListener('change', e => {
|
||||||
if (e.target.value === 'approve') {
|
if (e.target.value === 'approve') {
|
||||||
getRef('address_manage_title').textContent = 'KYCs to be approved'
|
showChildElement('kyc_mode_wrapper', 0)
|
||||||
getRef('submit_kyc').textContent = 'Approve'
|
|
||||||
} else {
|
} else {
|
||||||
getRef('address_manage_title').textContent = 'KYCs to be revoked'
|
showChildElement('kyc_mode_wrapper', 1)
|
||||||
getRef('submit_kyc').textContent = 'Revoke'
|
render.approvedKycs()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
getRef('search_approved').addEventListener('input', e => {
|
||||||
|
const searchKey = e.target.value.toLowerCase()
|
||||||
|
getRef('approved_kyc_addresses').querySelectorAll('li').forEach(li => {
|
||||||
|
if (li.dataset.searchKey.toLowerCase().includes(searchKey)) {
|
||||||
|
li.classList.remove('hidden')
|
||||||
|
} else {
|
||||||
|
li.classList.add('hidden')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
getRef('approved_kyc_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)
|
||||||
|
}
|
||||||
|
if (addressesToRevoke.size) {
|
||||||
|
getRef('selected_addresses').textContent = `${addressesToRevoke.size} selected`
|
||||||
|
getRef('revoke_kyc_button').parentElement.classList.remove('hidden')
|
||||||
|
} else {
|
||||||
|
getRef('selected_addresses').textContent = ``
|
||||||
|
getRef('revoke_kyc_button').parentElement.classList.add('hidden')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
function clearSelection() {
|
||||||
|
addressesToRevoke.clear()
|
||||||
|
getRef('selected_addresses').textContent = ``
|
||||||
|
getRef('revoke_kyc_button').parentElement.classList.add('hidden')
|
||||||
|
getRef('approved_kyc_addresses').querySelectorAll('input').forEach(input => input.checked = false)
|
||||||
|
}
|
||||||
getRef('aggregator_private_key').addEventListener('input', e => {
|
getRef('aggregator_private_key').addEventListener('input', e => {
|
||||||
checkBalance(floCrypto.getAddress(e.target.value.trim()))
|
checkBalance(floCrypto.getAddress(e.target.value.trim()))
|
||||||
})
|
})
|
||||||
@ -231,56 +366,98 @@
|
|||||||
addressInputObserver.observe(getRef('kyc_addresses_container'), {
|
addressInputObserver.observe(getRef('kyc_addresses_container'), {
|
||||||
childList: true
|
childList: true
|
||||||
})
|
})
|
||||||
function processAddresses() {
|
function processAddresses(addresses = []) {
|
||||||
const addressInputs = document.querySelectorAll('.kyc-address')
|
const uniqueAddresses = new Set()
|
||||||
const addresses = new Set()
|
addresses.forEach(address => {
|
||||||
addressInputs.forEach(input => {
|
let equivalentBtcAddress = address
|
||||||
const address = input.value.trim()
|
if (floCrypto.validateFloID(address))
|
||||||
if (address !== '') {
|
equivalentBtcAddress = btcOperator.convert.legacy2bech(address)
|
||||||
let equivalentBtcAddress = address
|
uniqueAddresses.add(equivalentBtcAddress)
|
||||||
if (floCrypto.validateFloID(address))
|
|
||||||
equivalentBtcAddress = btcOperator.convert.legacy2bech(address)
|
|
||||||
addresses.add(equivalentBtcAddress)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
return [...addresses]
|
return [...uniqueAddresses]
|
||||||
}
|
}
|
||||||
function submitAddresses() {
|
function approveAddresses() {
|
||||||
const aggregatorPrivateKey = getRef('aggregator_private_key').value.trim()
|
const aggregatorPrivateKey = getRef('aggregator_private_key').value.trim()
|
||||||
|
if (!aggregatorPrivateKey) {
|
||||||
|
return notify('Enter aggregator private key', 'error')
|
||||||
|
}
|
||||||
const aggregatorAddress = floCrypto.getAddress(aggregatorPrivateKey)
|
const aggregatorAddress = floCrypto.getAddress(aggregatorPrivateKey)
|
||||||
if (!floGlobals.approvedKycAggregators.hasOwnProperty(aggregatorAddress)) {
|
if (!floGlobals.approvedKycAggregators.hasOwnProperty(aggregatorAddress)) {
|
||||||
notify('KYC aggregator address is not approved', 'error')
|
return notify('KYC aggregator address is not approved', 'error')
|
||||||
return
|
|
||||||
}
|
}
|
||||||
const addresses = processAddresses()
|
const addressInputs = [...document.querySelectorAll('.kyc-address')]
|
||||||
|
.filter(input => input.value.trim() !== '')
|
||||||
|
const addresses = processAddresses(addressInputs)
|
||||||
if (addresses.length === 0) {
|
if (addresses.length === 0) {
|
||||||
notify('No addresses to process', 'error')
|
return notify('Enter at least one address to approve', 'error')
|
||||||
return
|
|
||||||
}
|
}
|
||||||
const kycType = getRef('kyc_type').value
|
const floData = `KYC|APPROVE_KYC|${addresses.join(',')}`
|
||||||
const floData = `KYC|${kycType === 'approve' ? 'APPROVE_KYC' : 'REVOKE_KYC'}|${addresses.join(',')}`
|
|
||||||
if (floData.length > 1040) {
|
if (floData.length > 1040) {
|
||||||
notify('Too many addresses. Try removing one and resubmitting.', 'error')
|
return notify('Too many addresses. Try removing one and resubmitting.', 'error')
|
||||||
return
|
|
||||||
}
|
}
|
||||||
buttonLoader(getRef('submit_kyc'), true)
|
getConfirmation('Approve entered addresses?', {
|
||||||
floBlockchainAPI.writeData(aggregatorAddress, floData, aggregatorPrivateKey, aggregatorAddress).then(txId => {
|
confirmText: 'Approve',
|
||||||
notify(`${kycType === 'approve' ? 'Approval' : 'Revoke'} request submitted. TXID: ${txId}`, 'success')
|
}).then((res) => {
|
||||||
getRef('kyc_addresses_container').innerHTML = '';
|
if (!res) return
|
||||||
getRef('kyc_form').reset()
|
buttonLoader(getRef('submit_kyc'), true)
|
||||||
addAddressInput()
|
floBlockchainAPI.writeData(aggregatorAddress, floData, aggregatorPrivateKey, aggregatorAddress).then(txId => {
|
||||||
}).catch(e => {
|
notify(`Approval request submitted. TXID: ${txId}`, 'success')
|
||||||
notify(e, 'error')
|
getRef('kyc_addresses_container').innerHTML = '';
|
||||||
}).finally(() => {
|
getRef('aggregator_private_key').value = ''
|
||||||
buttonLoader(getRef('submit_kyc'), false)
|
addAddressInput()
|
||||||
setTimeout(() => {
|
}).catch(e => {
|
||||||
checkBalance()
|
notify(e, 'error')
|
||||||
}, 1000)
|
}).finally(() => {
|
||||||
|
buttonLoader(getRef('submit_kyc'), false)
|
||||||
|
setTimeout(() => {
|
||||||
|
checkBalance()
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function revokeAddresses() {
|
||||||
|
const aggregatorPrivateKey = getRef('aggregator_private_key').value.trim()
|
||||||
|
if (aggregatorPrivateKey === '') {
|
||||||
|
return notify('Please enter KYC aggregator private key', 'error')
|
||||||
|
}
|
||||||
|
const aggregatorAddress = floCrypto.getAddress(aggregatorPrivateKey)
|
||||||
|
if (!floGlobals.approvedKycAggregators.hasOwnProperty(aggregatorAddress)) {
|
||||||
|
return notify('KYC aggregator address is not approved', 'error')
|
||||||
|
}
|
||||||
|
const addresses = processAddresses([...addressesToRevoke])
|
||||||
|
if (addresses.length === 0) {
|
||||||
|
return notify('Select at least one address to revoke', 'error')
|
||||||
|
}
|
||||||
|
getConfirmation('Revoke selected addresses?', {
|
||||||
|
confirmText: 'Revoke',
|
||||||
|
}).then((res) => {
|
||||||
|
if (!res) return
|
||||||
|
const floData = `KYC|REVOKE_KYC|${addresses.join(',')}`
|
||||||
|
buttonLoader(getRef('revoke_kyc_button'), true)
|
||||||
|
floBlockchainAPI.writeData(aggregatorAddress, floData, aggregatorPrivateKey, aggregatorAddress).then(txId => {
|
||||||
|
notify(`Revoke request submitted. TXID: ${txId}`, 'success')
|
||||||
|
getRef('aggregator_private_key').value = ''
|
||||||
|
addressesToRevoke.forEach(address => {
|
||||||
|
if (!floGlobals.approvedKyc.hasOwnProperty(address)) return
|
||||||
|
floGlobals.approvedKyc[address].revokedBy = aggregatorAddress
|
||||||
|
floGlobals.approvedKyc[address].validTo = Date.now()
|
||||||
|
})
|
||||||
|
addressesToRevoke.clear()
|
||||||
|
render.approvedKycs()
|
||||||
|
}).catch(e => {
|
||||||
|
notify(e, 'error')
|
||||||
|
}).finally(() => {
|
||||||
|
buttonLoader(getRef('revoke_kyc_button'), false)
|
||||||
|
setTimeout(() => {
|
||||||
|
checkBalance()
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
window.onload = async () => {
|
window.onload = async () => {
|
||||||
try {
|
try {
|
||||||
await getApprovedAggregators()
|
await getApprovedAggregators()
|
||||||
|
await getApprovedKycs()
|
||||||
router.routeTo(window.location.hash)
|
router.routeTo(window.location.hash)
|
||||||
getRef('aggregator_private_key').customValidation = floCrypto.getPubKeyHex
|
getRef('aggregator_private_key').customValidation = floCrypto.getPubKeyHex
|
||||||
addAddressInput()
|
addAddressInput()
|
||||||
|
|||||||
@ -19,6 +19,61 @@ 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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Use when a function needs to be executed after user finishes changes
|
||||||
|
const debounce = (callback, wait) => {
|
||||||
|
let timeoutId = null;
|
||||||
|
return (...args) => {
|
||||||
|
window.clearTimeout(timeoutId);
|
||||||
|
timeoutId = window.setTimeout(() => {
|
||||||
|
callback.apply(null, args);
|
||||||
|
}, wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
class Router {
|
class Router {
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
@ -83,8 +138,8 @@ function loading(show = true) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
floGlobals.approvedKycAggregators = {};
|
|
||||||
function getApprovedAggregators() {
|
function getApprovedAggregators() {
|
||||||
|
floGlobals.approvedKycAggregators = {};
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
floBlockchainAPI.readAllTxs(floGlobals.masterAddress).then(txs => {
|
floBlockchainAPI.readAllTxs(floGlobals.masterAddress).then(txs => {
|
||||||
txs.filter(tx => floCrypto.isSameAddr(tx.vin[0].addr, floGlobals.masterAddress) && tx.floData.startsWith('KYC'))
|
txs.filter(tx => floCrypto.isSameAddr(tx.vin[0].addr, floGlobals.masterAddress) && tx.floData.startsWith('KYC'))
|
||||||
@ -116,4 +171,44 @@ function getApprovedAggregators() {
|
|||||||
reject(e);
|
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,
|
||||||
|
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);
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
1
scripts/components.min.js
vendored
1
scripts/components.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user