/*jshint esversion: 8 */ /** * @yaireo/relative-time - javascript function to transform timestamp or date to local relative-time * * @version v1.0.0 * @homepage https://github.com/yairEO/relative-time */ !function (e, t) { var o = o || {}; "function" == typeof o && o.amd ? o([], t) : "object" == typeof exports && "object" == typeof module ? module.exports = t() : "object" == typeof exports ? exports.RelativeTime = t() : e.RelativeTime = t() }(this, (function () { const e = { year: 31536e6, month: 2628e6, day: 864e5, hour: 36e5, minute: 6e4, second: 1e3 }, t = "en", o = { numeric: "auto" }; function n(e) { e = { locale: (e = e || {}).locale || t, options: { ...o, ...e.options } }, this.rtf = new Intl.RelativeTimeFormat(e.locale, e.options) } return n.prototype = { from(t, o) { const n = t - (o || new Date); for (let t in e) if (Math.abs(n) > e[t] || "second" == t) return this.rtf.format(Math.round(n / e[t]), t) } }, n })); const relativeTime = new RelativeTime({ style: 'narrow' }); // use floDapps.storeContact() to store contacts that can be used by other apps on same device async function syncUserData(obsName, data) { floDapps.user.private.then(privateKey => { if (!privateKey) return; const encryptedData = Crypto.AES.encrypt(JSON.stringify(data), privateKey); return floCloudAPI.sendApplicationData(encryptedData, obsName, { receiverID: floDapps.user.id }); }).catch(error => { console.log(error); notify('Invalid password', 'error'); return false; }) } // store user data in separate IDB async function organizeSyncedData(obsName) { const fetchedData = await floCloudAPI.requestApplicationData(obsName, { mostRecent: true, senderIDs: [floDapps.user.id], receiverID: floDapps.user.id }); if (fetchedData.length && await compactIDB.readData(obsName, 'lastSyncTime') !== fetchedData[0].time) { await compactIDB.clearData(obsName); const dataToDecrypt = floCloudAPI.util.decodeMessage(fetchedData[0].message); floDapps.user.private.then(privateKey => { if (!privateKey) return; const decryptedData = JSON.parse(Crypto.AES.decrypt(dataToDecrypt, privateKey)); for (let key in decryptedData) { floGlobals[obsName][key] = decryptedData[key]; compactIDB.addData(obsName, decryptedData[key], key); } compactIDB.addData(obsName, fetchedData[0].time, 'lastSyncTime'); return true; }).catch(error => { console.log(error); notify('Invalid password', 'error'); return false; }) } else { const idbData = await compactIDB.readAllData(obsName); for (const key in idbData) { if (key !== 'lastSyncTime') floGlobals[obsName][key] = idbData[key]; } return true; } } const userUI = {}; function continueWalletTopup() { let cashier = User.findCashier(); if (!cashier) return notify("No cashier online. Please try again in a while.", 'error'); // const upiId = getRef('select_topup_upi_id').value; floGlobals.txCode = randomString(6); // if (!upiId) // return notify("Please add the UPI ID which you'll use to send the money", 'error'); let amount = parseFloat(getRef('request_cashier_amount').value.trim()); getRef('topup_wallet__upi_id').value = cashierUPI[cashier]; getRef('topup_wallet__qr_code').innerHTML = '' getRef('topup_wallet__qr_code').append(new QRCode({ msg: `upi://pay?pn=FLOPay&pa=${cashierUPI[cashier]}&am=${amount}&tn=${floGlobals.txCode}`, ecl: 'H' })) render.conditionalSteps() showChildElement('topup_wallet_process', 1) // getRef('topup_wallet__txid').focusIn(); } function depositMoneyToWallet() { let cashier = User.findCashier(); if (!cashier) return notify("No cashier online. Please try again in a while.", 'error'); let amount = parseFloat(getRef('request_cashier_amount').value.trim()); // let upiTxID = getRef('topup_wallet__txid').value.trim(); // const txCode = getRef('topup_wallet__code').value; // const upiId = getRef('select_topup_upi_id').value; // if (upiTxID === '') // return notify("Please enter UPI transaction ID", 'error'); buttonLoader('topup_wallet_button', true); User.cashToToken(cashier, amount, floGlobals.txCode/* , upiId */).then(result => { console.log(result); showChildElement('topup_wallet_process', 2); refreshBalance() }).catch(error => { console.error(error) getRef('topup_failed_reason').textContent = error; showChildElement('topup_wallet_process', 3); }).finally(() => { buttonLoader('topup_wallet_button', false); }); } getRef('topup_wallet__qr_wrapper').addEventListener('toggle', e => render.conditionalSteps()) function withdrawMoneyFromWallet() { let cashier = User.findCashier(); if (!cashier) return notify("No cashier online. Please try again in a while.", 'error'); let amount = parseFloat(getRef('send_cashier_amount').value.trim()); const upiId = getRef('select_withdraw_upi_id').value; if (!upiId) return notify("Please add an UPI ID to continue", 'error'); buttonLoader('withdraw_rupee_button', true); getRef('withdrawal_blockchain_link').classList.add('hidden'); User.sendToken(cashier, amount, 'for token-to-cash').then(txid => { console.warn(`Withdraw ${amount} from cashier ${cashier}`, txid); User.tokenToCash(cashier, amount, txid, upiId).then(result => { showChildElement('withdraw_wallet_process', 1); refreshBalance(); getRef('withdrawal_blockchain_link').classList.remove('hidden'); getRef('withdrawal_blockchain_link').href = `https://flosight.duckdns.org/tx/${txid}` console.log(result); }).catch(error => { getRef('withdrawal_failed_reason').textContent = error; showChildElement('withdraw_wallet_process', 2); console.error(error) }).finally(() => { buttonLoader('withdraw_rupee_button', false); }); }).catch(error => { getRef('withdrawal_failed_reason').textContent = error; showChildElement('withdraw_wallet_process', 2); buttonLoader('withdraw_rupee_button', false); console.error(error) }) } function transferToExchange() { const amount = parseFloat(getRef('exchange_transfer__amount').value.trim()); buttonLoader('exchange_transfer__button', true); floDapps.user.private.then(privateKey => { if (!privateKey) return; floExchangeAPI.depositToken('rupee', amount, floDapps.user.id, 'FRJkPqdbbsug3TtQRAWviqvTL9Qr2EMnrm', privateKey).then(txid => { console.log(txid); showChildElement('exchange_transfer_process', 1); getRef('exchange_transfer__success_message').textContent = `Transferred ${formatAmount(amount)} to exchange`; }).catch(error => { console.log(error); if (error.code) { error = error.message; } if (error === 'Insufficient rupee# balance') error = 'Insufficient rupee token balance in your wallet, please top-up your wallet.'; getRef('exchange_transfer__failed_reason').textContent = error; showChildElement('exchange_transfer_process', 2); }).finally(() => { buttonLoader('exchange_transfer__button', false); }); }).catch(error => { console.log(error); notify('Invalid password', 'error'); closePopup(); return false; }) } async function renderSavedUpiIds() { let savedUpiIds = [] for (const upiId in floGlobals.savedUserData.upiIds) { savedUpiIds.push(render.savedUpiId(upiId)) } renderElem(getRef('saved_upi_ids_list'), html`${savedUpiIds}`) } function saveUpiId() { const frag = document.createDocumentFragment(); const upiId = getRef('get_upi_id').value.trim(); if (upiId === '') return notify("Please add an UPI ID to continue", 'error'); if (floGlobals.savedUserData.upiIds.hasOwnProperty(upiId)) return notify('This UPI ID is already saved', 'error'); floGlobals.savedUserData.upiIds[upiId] = {} syncUserData('savedUserData', floGlobals.savedUserData).then(() => { notify(`Saved ${upiId}`, 'success'); if (pagesData.lastPage === 'settings') { getRef('saved_upi_ids_list').append(html.node`${render.savedUpiId(upiId)}`); } else if (pagesData.lastPage === 'home') { getRef('select_withdraw_upi_id').append(render.savedUpiIdOption(upiId)); getRef('select_withdraw_upi_id').parentNode.classList.remove('hidden') } closePopup(); }).catch(error => { notify(error, 'error'); }) } delegate(getRef('saved_upi_ids_list'), 'click', '.saved-upi', e => { if (e.target.closest('.delete-upi')) { const upiId = e.delegateTarget.dataset.upiId; getConfirmation('Do you want delete this UPI ID?', { confirmText: 'Delete', }).then(res => { if (res) { const toDelete = getRef('saved_upi_ids_list').querySelector(`.saved-upi[data-upi-id="${upiId}"]`); if (toDelete) toDelete.remove(); delete floGlobals.savedUserData.upiIds[upiId]; closePopup(); syncUserData('savedUserData', floGlobals.savedUserData).then(() => { notify(`Deleted UPI ID`, 'success'); }).catch(error => { notify(error, 'error'); }); } }); } }); userUI.renderCashierRequests = function (requests, error = null) { if (error) return console.error(error); else if (typeof requests !== "object" || requests === null) return; let processedRequests = 0; for (let transactionID in requests) { const { message: {amount,mode}, note, tag } = requests[transactionID]; let status = tag ? 'completed' : (note ? 'rejected' : "pending"); // console.log(requests[transactionID]) if (status !== 'pending') { processedRequests++; } if (pagesData.lastPage === 'wallet') { getRef('wallet_history_wrapper').querySelectorAll(`[data-vc="${transactionID}"]`).forEach(card => card.remove()); getRef(status !== 'pending' ? 'wallet_history' : 'pending_wallet_transactions').prepend(render.walletRequestCard(requests[transactionID])) } if (floGlobals.loaded&& status !== 'pending') { const { message: {amount,mode}, note, tag } = requests[transactionID]; notify(`Your ${mode ==='cash-to-token'? 'top-up': 'withdraw'} request of ${formatAmount(amount)} has been ${status}`, status === 'completed' ? 'success' : 'error', { action: { label: 'View', callback: () => { window.location.hash = `#/wallet` } } }); } } if(pagesData.lastPage !== 'wallet') { if(processedRequests === 0) removeNotificationBadge('wallet_history_button'); else { addNotificationBadge('wallet_history_button', processedRequests) } } }; const pendingTransactionsObserver = new MutationObserver((mutations) => { mutations.forEach(mutation => { if (mutation.type === 'childList') { if (mutation.target.children.length) mutation.target.parentNode.classList.remove('hidden') else mutation.target.parentNode.classList.add('hidden') } }) }); userUI.renderMoneyRequests = function (requests, error = null) { if (error) return console.error(error); else if (typeof requests !== "object" || requests === null) return; if (pagesData.lastPage === 'requests') { for (let r in requests) { getRef('requests_history_wrapper').querySelectorAll(`[data-vc="${r}"]`).forEach(card => card.remove()); if (requests[r].note) { getRef('payment_request_history').prepend(render.paymentRequestCard(requests[r])); } else { getRef('pending_payment_requests').prepend(render.paymentRequestCard(requests[r])); } } } if (floGlobals.loaded) { for (let r in requests) { if (!requests[r].note) { notify(`You have received payment request from ${getFloIdTitle(requests[r].senderID)}`, '', { action: { label: 'View', callback: () => { window.location.hash = `#/requests` } } }); } } } let totalRequests = 0; for (const request in User.moneyRequests) { if (!User.moneyRequests[request].note) totalRequests++; } if (totalRequests) { addNotificationBadge('requests_page_button',totalRequests) } else { removeNotificationBadge('requests_page_button') } }; function addNotificationBadge(elem, text) { const animOptions = { duration: 200, fill: 'forwards', easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)' } if (!getRef(elem).querySelector('.badge')) { const badge = createElement('span', { className: 'badge', textContent: text }) getRef(elem).append(badge) badge.animate([ { transform: 'scale(0) translateY(0.5rem)' }, { transform: 'scale(1) translateY(0)' }, ], animOptions) } else { const badge = getRef(elem).querySelector('.badge'); badge.textContent = text; badge.animate([ { transform: 'scale(1)' }, { transform: `scale(1.5)` }, { transform: 'scale(1)' } ], animOptions) } } function removeNotificationBadge(elem) { const animOptions = { duration: 200, fill: 'forwards', easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)' } if (getRef(elem).querySelector('.badge')) { const badge = getRef(elem).querySelector('.badge') badge.animate([ { transform: 'scale(1) translateY(0)' }, { transform: 'scale(0) translateY(0.5rem)' }, ], animOptions).onfinish = () => { badge.remove() } } } userUI.payRequest = function (reqID) { let { message: { amount, remark }, senderID } = User.moneyRequests[reqID]; getConfirmation('Pay?', { message: `Do you want to pay ${amount} to ${senderID}?`, confirmText: 'Pay' }).then(confirmation => { if (confirmation) { User.sendToken(senderID, amount, "|" + remark).then(txid => { console.warn(`Sent ${amount} to ${senderID}`, txid); notify(`Sent ${formatAmount(amount)} to ${getFloIdTitle(senderID)}. It may take a few mins to reflect in their wallet`, 'success'); User.decideRequest(User.moneyRequests[reqID], 'PAID: ' + txid) .then(result => console.log(result)) .catch(error => console.error(error)) }).catch(error => console.error(error)); } }) } userUI.declineRequest = function (reqID) { let request = User.moneyRequests[reqID]; getConfirmation('Decline payment?', { confirmText: 'Decline' }).then(confirmation => { if (confirmation) { User.decideRequest(request, "DECLINED").then(result => { console.log(result); notify("Request declined", 'success'); }).catch(error => console.error(error)) } }) } delegate(getRef('pending_payment_requests'), 'click', '.pay-requested', e => { const vectorClock = e.target.closest('.payment-request').dataset.vc; userUI.payRequest(vectorClock); }) delegate(getRef('pending_payment_requests'), 'click', '.decline-payment', e => { const vectorClock = e.target.closest('.payment-request').dataset.vc; userUI.declineRequest(vectorClock); }) //Cashier const cashierUI = {}; cashierUI.renderRequests = function (requests, error = null) { if (error) return console.error(error); else if (typeof requests !== "object" || requests === null) return; for (let transactionID in requests) { const { note, tag } = requests[transactionID]; let status = tag ? 'done' : (note ? 'failed' : "pending"); getRef('cashier_requests_wrapper').querySelectorAll(`[data-vc="${transactionID}"]`).forEach(card => card.remove()); getRef(status === 'pending' ? 'cashier_pending_request_list' : 'cashier_processed_request_list').prepend(render.cashierRequestCard(requests[transactionID])) } } cashierUI.completeRequest = function (reqID) { floGlobals.cashierProcessingRequest = Cashier.Requests[reqID]; const { message: { mode } } = floGlobals.cashierProcessingRequest; if (mode === "cash-to-token") completeCashToTokenRequest(floGlobals.cashierProcessingRequest); else if (mode === "token-to-cash") completeTokenToCashRequest(floGlobals.cashierProcessingRequest); } function completeCashToTokenRequest(request) { const { message: { upi_txid, amount, upiID, txCode }, vectorClock } = request; getRef('top_up_amount').textContent = formatAmount(amount); getRef('top_up__code').value = txCode; openPopup('confirm_topup_popup'); // Cashier.checkIfUpiTxIsValid(upi_txid).then(_ => { // getRef('top_up_amount').textContent = formatAmount(amount); // // getRef('top_up_txid').value = upi_txid; // // getRef('top_up_upi_id').value = upiID; // getRef('top_up__code').value = txCode; // openPopup('confirm_topup_popup'); // }).catch(error => { // notify(Array.isArray(error) ? error[1]: error, 'error'); // if (Array.isArray(error) && error[0] === true && typeof error[1] === 'string') // Cashier.rejectRequest(request, error[1]).then(result => { // console.log(result); // console.info('Rejected cash-to-token request:', vectorClock); // }).catch(error => console.error(error)) // }) } function confirmTopUp(button) { const { message: { amount }, vectorClock, senderID } = floGlobals.cashierProcessingRequest; var tokenAmt = amount; buttonLoader(button, true); floBlockchainAPI.getBalance(senderID).then(async user_balance => { let floAmt = floGlobals.sendAmt; if (user_balance < floGlobals.settings.user_flo_threshold) { let cur_rate = (await floExchangeAPI.getRates("FLO")).rate; floAmt = floGlobals.settings.send_user_flo; tokenAmt -= cur_rate * floAmt; } User.sendToken(senderID, tokenAmt, 'for cash-to-token', { sendAmt: floAmt }).then(txid => { console.warn(`${amount} (${tokenAmt}|${floAmt}) cash-to-token for ${senderID}`, txid); let note = txid + (tokenAmt === amount ? '' : `#${tokenAmt}`); Cashier.finishRequest(floGlobals.cashierProcessingRequest, note).then(result => { console.log(result); console.info('Completed cash-to-token request:', vectorClock); notify("Completed request", 'success'); closePopup() }).catch(error => console.error(error)) .finally(() => buttonLoader(button, false)); }).catch(error => { console.error(error) buttonLoader(button, false); }) }).catch(error => { buttonLoader(button, false); console.error(error) }) } getRef('top_up__reason_selector').addEventListener('change', e => { console.log(e.target.value); if (e.target.value === 'other') { getRef('top_up__specified_reason').parentNode.classList.remove('hidden'); } else { getRef('top_up__specified_reason').parentNode.classList.add('hidden'); } }) function declineTopUp() { const { vectorClock } = floGlobals.cashierProcessingRequest; let reason = getRef('top_up__reason_selector').value; if (reason === 'other') { reason = getRef('top_up__specified_reason').value } if (reason.trim() === '') return notify('Please specify a reason', 'error'); buttonLoader('decline_button', true); Cashier.rejectRequest(floGlobals.cashierProcessingRequest, reason).then(result => { console.log(result); console.info('Rejected cash-to-token request:', vectorClock); notify('Rejected top-up request', 'success'); closePopup() }).catch(error => console.error(error)).finally(() => buttonLoader('decline_button', false)); } async function completeTokenToCashRequest(request) { const { vectorClock, senderID, message: { token_txid, amount, upi_id } } = request; var upiID; if (upi_id instanceof Object && "secret" in upi_id) { try { const privateKey = await floGlobals.user.private upiID = floCrypto.decryptData(upi_id, privateKey); } catch (error) { console.error("UPI ID is not encrypted with a proper key", error); return notify("Invalid UPI ID", 'error'); } } else upiID = upi_id; Cashier.checkIfTokenTxIsValid(token_txid, senderID, amount).then(result => { getPromptInput('Process', `Token transfer is verified!\n Send ${formatAmount(amount)}\n to ${upiID}\n Enter UPI transaction ID`, { placeholder: 'UPI transaction ID', }).then(upiTxID => { if (!upiTxID || upiTxID.length < 10) return notify("Invalid UPI txid", 'error'); Cashier.finishRequest(request, upiTxID).then(result => { console.log(result); console.info('Completed token-to-cash request:', vectorClock); notify("Completed request", 'success'); }).catch(error => console.error(error)) }) }).catch(error => { notify(error, 'error'); if (Array.isArray(error) && error[0] === true && typeof error[1] === 'string') Cashier.rejectRequest(request, error[1]).then(result => { console.log(result); console.info('Rejected token-to-cash request:', vectorClock); }).catch(error => console.error(error)) }) } function getFloIdTitle(floID) { return floGlobals.savedIds[floID] ? floGlobals.savedIds[floID].title : floID; } function formatAmount(amount = 0, currency = 'inr') { if (!amount) return '₹0.00'; return amount.toLocaleString(currency === 'inr'? `en-IN`: 'en-US', { style: 'currency', currency }) } const cashierRejectionErrors = { 1001: `Your request was reject because of wrong transaction ID. If you have sent money, it'll be returned within 24 hrs.`, 1002: `Amount requested and amount sent via UPI doesn't match. your transferred money will be returned within 24hrs.`, 1003: `Your request was rejected because of wrong or missing remark/message code. If you have sent money, it'll be returned within 24 hrs.`, 1004: `Your request was rejected because specified amount wasn't received by the cashier.`, } const render = { savedId(floID, details,ref) { const { title } = details; return html.for(ref,floID)`
  • ${title}
    ${floID}
  • `; }, rupeeTxCard(transactionDetails) { const { txid, time, sender, receiver, tokenAmount } = transactionDetails; let transactionReceiver let className let icon if (sender === floDapps.user.id) { className = 'transaction grid sent' transactionReceiver = `Sent to ${getFloIdTitle(receiver) || 'Myself'}`; icon = svg``; } else if (receiver === floDapps.user.id) { className = 'transaction grid received' transactionReceiver = `Received from ${getFloIdTitle(sender)}`; icon = svg``; } else { //This should not happen unless API returns transaction that does not involve floDapps.user.id return html`sender: ${sender} | receiver: ${receiver}`; } return html.node`
  • ${icon}
    ${transactionReceiver}
    ${formatAmount(tokenAmount)}
  • `; }, cashierRequestCard(details) { const { time, senderID, message: { mode, amount = 0 }, note, tag, vectorClock } = details; const clone = getRef('cashier_request_template').content.cloneNode(true).firstElementChild; clone.dataset.vc = vectorClock; const status = tag || note; //status tag for completed, note for rejected clone.querySelector('.cashier-request__details').textContent = `${mode === 'cash-to-token' ? 'Top-up wallet with' : 'Withdraw'} ${formatAmount(amount)}`; clone.querySelector('.cashier-request__requestor').textContent = senderID; clone.querySelector('.cashier-request__time').textContent = getFormattedTime(time); clone.querySelector('.cashier-request__mode').innerHTML = mode === 'token-to-cash' ? ` ` : ``; if (status) clone.querySelector('.cashier-request__status').textContent = status.includes(':') ? status.split(':')[0] : status; else clone.querySelector('.cashier-request__status').innerHTML = ``; return clone; }, walletRequestCard(details) { const { time, message: { mode, amount }, note, tag, vectorClock } = details; const clone = getRef('wallet_request_template').content.cloneNode(true).firstElementChild.firstElementChild; const type = mode === 'cash-to-token' ? 'Top-up' : 'Withdraw'; let status = tag ? tag : (note ? 'REJECTED' : "PENDING"); clone.classList.add(status.toLowerCase()); clone.classList.add(mode === 'cash-to-token' ? 'added' : 'withdrawn'); clone.dataset.vc = vectorClock; clone.href = `#/transaction?transactionId=${vectorClock}&type=wallet`; clone.querySelector('.wallet-request__icon').innerHTML = mode === 'cash-to-token' ? `` : ``; clone.querySelector('.wallet-request__details').textContent = type; clone.querySelector('.wallet-request__amount').textContent = formatAmount(amount); clone.querySelector('.wallet-request__time').textContent = getFormattedTime(time); let icon = ''; if (status === 'REJECTED') { icon = `` clone.querySelector('.wallet-request__status').innerHTML = `Failed ${icon}`; clone.querySelector('.wallet-request__status').classList.add('capitalize') } if (status === 'COMPLETED' && note.includes('#')) { const [txid,finalAmount] = note.split('#'); clone.querySelector('.wallet-request__amount').textContent = formatAmount(parseFloat(finalAmount)); clone.querySelector('.wallet-request__status').innerHTML = `+ 1 FLO  worth ${formatAmount(amount - parseFloat(finalAmount))}`; } return clone; }, paymentRequestCard(details) { const { time, senderID, message: { amount, remark }, note, vectorClock } = details; const clone = getRef(`${note ? 'processed' : 'pending'}_payment_request_template`).content.cloneNode(true).firstElementChild; clone.dataset.vc = vectorClock; clone.querySelector('.payment-request__requestor').textContent = getFloIdTitle(senderID); clone.querySelector('.payment-request__remark').textContent = remark; clone.querySelector('.payment-request__time').textContent = getFormattedTime(time); clone.querySelector('.payment-request__amount').textContent = amount.toLocaleString(`en-IN`, { style: 'currency', currency: 'INR' }); const status = note ? note.split(':')[0] : 'PENDING'; if (note) { clone.firstElementChild.href = `#/transaction?transactionId=${vectorClock}&type=request`; let icon if (status === 'PAID') icon = `` else icon = `` clone.querySelector('.payment-request__status').innerHTML = `${status.toLowerCase()} ${icon}`; } return clone; }, transactionMessage(details) { const { tokenAmount, time, sender, receiver, flodata } = floTokenAPI.util.parseTxData(details) let messageType = sender === receiver ? 'self' : sender === floDapps.user.id ? 'sent' : 'received'; const clone = getRef('transaction_message_template').content.cloneNode(true).firstElementChild; clone.classList.add(messageType); clone.querySelector('.transaction-message__amount').textContent = formatAmount(tokenAmount); if (flodata.split('|')[1]) { clone.querySelector('.transaction-message__remark').textContent = flodata.split('|')[1]; } clone.querySelector('.transaction-message__time').textContent = getFormattedTime(time * 1000); return clone; }, savedUpiId(upiId) { return html`
  • ${upiId}
  • `; }, savedIdPickerCard(floID, { title }) { return html`
  • ${title[0]}

    ${title}

    ${floID}
  • ` }, savedUpiIdOption(upiId) { return createElement('sm-option', { textContent: upiId, attributes: { value: upiId, } }) }, rupeeHistory() { let paymentTransactions = [] if (rupeeHistoryLoader) rupeeHistoryLoader.clear() getRef('payments_history').innerHTML = ''; floTokenAPI.getAllTxs(floDapps.user.id).then(({ transactions }) => { for (const transactionId in transactions) { paymentTransactions.push({ ...floTokenAPI.util.parseTxData(transactions[transactionId]), txid: transactionId }) } const filter = getRef('payments_type_filter').querySelector('input:checked').value; if (filter !== 'all') { let propToCheck = filter === 'sent' ? 'sender' : 'receiver'; paymentTransactions = paymentTransactions.filter(v => v[propToCheck] === floDapps.user.id) } // solve sorting issue at backend paymentTransactions.sort((a, b) => b.time - a.time); if (rupeeHistoryLoader) { rupeeHistoryLoader.update(paymentTransactions); } else { rupeeHistoryLoader = new LazyLoader('#payments_history', paymentTransactions, render.rupeeTxCard); } rupeeHistoryLoader.init(); }).catch(e => { console.error(e) }) }, btcTxCard(transactionDetails) { let { amount, time, txid, sender, receiver, type, block } = transactionDetails; let transactionReceiver let icon // block = null if (block) { if (type === 'out') { transactionReceiver = `Sent to ${receiver}`; icon = svg``; } else if (type === 'in') { transactionReceiver = `Received from ${sender}`; icon = svg``; } else if (type === 'self') { transactionReceiver = `Sent to self`; icon = svg``; } } else { transactionReceiver = (type === 'out' ? `Sent to ${receiver}` : `Received from ${sender}`); icon = svg``; } const className = `btc-tx grid ${type} ${block === null ? 'unconfirmed-tx' : ''}` return html.node`
  • ${icon}
    ${formatAmount(amount, 'btc')}
    ${transactionReceiver}
    TXID: ${txid}
    ${!block ? html`

    Confirmation pending: amount will be deducted after transaction is confirmed

    ` : ''}
  • `; }, btcHistory() { try { // render transactions getRef('payments_history').innerHTML = ''; getAddressDetails( btc_api.convert.legacy2bech(floDapps.user.id)).then(result => { if (result.txs.length) { let allTransactions = result.txs; const filter = getRef('payments_type_filter').querySelector('input:checked').value; if (filter !== 'all') { allTransactions = allTransactions.filter(t => filter === 'sent' ? t.type === 'out' : t.type === 'in') } console.log(allTransactions) if (btcHistoryLoader) { btcHistoryLoader.update(allTransactions) } else { btcHistoryLoader = new LazyLoader('#payments_history', allTransactions, render.btcTxCard) } btcHistoryLoader.init() } else { getRef('payments_history').textContent = 'No transactions found'; } }).catch(error => console.error(error)) } catch (err) { notify(err, 'error'); } }, async savedIds() { await organizeSyncedData('savedIds'); renderElem(getRef('saved_ids_list'), html`${getArrayOfSavedIds().map(({floID, details}) => render.savedId(floID, details, getRef('saved_ids_list')))}`) }, conditionalSteps() { if (getRef('topup_wallet__qr_wrapper').open) { getRef('topup_steps').querySelectorAll('.conditional').forEach(e => e.remove()); } else { const amount = parseFloat(getRef('request_cashier_amount').value.trim()); if (getRef('topup_steps').children.length < 4) { const conditionalSteps = getRef('conditional_steps_template').content.cloneNode(true); conditionalSteps.querySelector('.amount').textContent = formatAmount(amount); conditionalSteps.querySelector('.tx-code').setAttribute('value', floGlobals.txCode); getRef('topup_steps').children[1].after(conditionalSteps) } else { getRef('topup_steps').querySelector('.amount').textContent = formatAmount(amount); getRef('topup_steps').querySelector('.tx-code').setAttribute('value', floGlobals.txCode); } } } }; function buttonLoader(id, show) { const button = typeof id === 'string' ? getRef(id) : id; button.disabled = show; const animOptions = { duration: 200, fill: 'forwards', easing: 'ease' } if (show) { button.animate([ { clipPath: 'circle(100%)', }, { clipPath: 'circle(0)', }, ], animOptions).onfinish = e => { e.target.commitStyles() e.target.cancel() } button.parentNode.append(createElement('sm-spinner')) } else { button.style = '' const potentialTarget = button.parentNode.querySelector('sm-spinner') if (potentialTarget) potentialTarget.remove(); } } async function refreshBalance(button) { if (button) buttonLoader(button, true) floTokenAPI.getBalance(floDapps.user.id).then((balance = 0) => { const [beforeDecimal, afterDecimal] = formatAmount(balance).split('₹')[1].split('.') renderElem(getRef('rupee_balance'), html`${beforeDecimal}.${afterDecimal}`) if (button) buttonLoader(button, false) }) btc_api.getBalance(btc_api.convert.legacy2bech(floDapps.user.id)).then(btcBalance => { if(btcBalance) { const [beforeDecimal, afterDecimal = '00'] = String(btcBalance).split('.') renderElem(getRef('btc_balance'), html`${beforeDecimal}.${afterDecimal}`) } else { renderElem(getRef('btc_balance'), html`0`) } }) try { const [floBal, floRates] = await Promise.all([floBlockchainAPI.getBalance(floDapps.user.id), floExchangeAPI.getRates('FLO')]) const [beforeDecimal, afterDecimal = '00'] = String(floBal).split('.') renderElem(getRef('flo_balance'), html`${beforeDecimal}.${afterDecimal}`) if (floBal < floGlobals.settings.user_flo_threshold) { getRef('low_user_flo_warning').textContent = `Your FLO balance is low. You will receive ${floGlobals.settings.send_user_flo} FLO of worth ₹${parseFloat(floRates.rate.toFixed(2))} deducted from top-up amount.`; getRef('low_user_flo_warning').classList.remove('hidden'); } else { getRef('low_user_flo_warning').classList.add('hidden'); } if (button) buttonLoader(button, false) } catch (e) { console.error(e) } } const assetIcons = { btc: ` `, usd: ``, rupee: `` } function setCorrectInputParams({fromAsset, toAsset}) { getRef('from_amount').setAttribute('min', fromAsset === 'rupee' ? '50' : '0.00000001'); getRef('from_amount').setAttribute('step', fromAsset === 'rupee' ? '0.01' : '0.00000001'); getRef('from_amount').setAttribute('error-text', `Minimum amount is ${fromAsset === 'rupee' ? '₹50' : '0.000001BTC'}`); getRef('to_amount').setAttribute('min', toAsset === 'rupee' ? '50' : '0.00000001'); getRef('to_amount').setAttribute('step', toAsset === 'rupee' ? '0.01' : '0.00000001'); getRef('to_amount').setAttribute('error-text', `Minimum amount is ${toAsset === 'rupee' ? '₹50' : '0.000001BTC'}`); getRef('from_asset_icon').innerHTML = assetIcons[fromAsset] getRef('to_asset_icon').innerHTML = assetIcons[toAsset] } getRef('from_asset_selector').addEventListener('change', e => { const fromAsset = e.target.value; const toAsset = fromAsset === 'rupee' ? 'btc' : 'rupee'; setCorrectInputParams({fromAsset, toAsset}); getRef('to_asset_selector').value = toAsset }) getRef('to_asset_selector').addEventListener('change', e => { const toAsset = e.target.value const fromAsset = toAsset === 'rupee' ? 'btc' : 'rupee'; setCorrectInputParams({fromAsset, toAsset}); getRef('from_asset_selector').value = fromAsset }) getRef('from_amount').addEventListener('input', e => { const fromAsset = getRef('from_asset_selector').value; let fromAmount = parseFloat(e.target.value.trim()); if (fromAmount && !Number.isNaN(fromAmount)) getRef('to_amount').value = fromAsset === 'rupee' ? parseFloat((fromAmount / globalExchangeRate.inr).toFixed(8)) : parseFloat((fromAmount * globalExchangeRate.inr).toFixed(2)) else getRef('to_amount').value = '' }) getRef('to_amount').addEventListener('input', e => { const toAsset = getRef('to_asset_selector').value; let toAmount = parseFloat(e.target.value.trim()); if (toAmount && !Number.isNaN(toAmount)) getRef('from_amount').value = toAsset === 'rupee' ? parseFloat((toAmount / globalExchangeRate.inr).toFixed(8)) : parseFloat((toAmount * globalExchangeRate.inr).toFixed(2)) else getRef('from_amount').value = '' }) function getArrayOfSavedIds() { const arr = []; for (const key in floGlobals.savedIds) { arr.push({ floID: key, details: floGlobals.savedIds[key] }); } return arr.sort((a, b) => a.details.title.localeCompare(b.details.title)); } async function saveFloId() { const floID = getRef('flo_id_to_save').value.trim(); if (floGlobals.savedIds.hasOwnProperty(floID)) return notify('This FLO ID is already saved', 'error'); const title = getRef('flo_id_title_to_save').value.trim(); floGlobals.savedIds[floID] = { title } buttonLoader('save_flo_id_button', true); syncUserData('savedIds', floGlobals.savedIds).then(() => { render.savedIds() notify(`Saved ${floID}`, 'success'); closePopup(); }).catch(error => { notify(error, 'error'); }).finally(() => { buttonLoader('save_flo_id_button', false); }) } delegate(getRef('saved_ids_list'), 'click', '.saved-id', e => { if (e.target.closest('.edit-saved')) { const target = e.target.closest('.saved-id'); console.log(target) getRef('edit_saved_id').setAttribute('value', target.dataset.floId); getRef('get_new_title').value = getFloIdTitle(target.dataset.floId); openPopup('edit_saved_popup'); } else if (e.target.closest('.copy-saved-id')) { const target = e.target.closest('.saved-id'); navigator.clipboard.writeText(target.dataset.floId) target.dispatchEvent( new CustomEvent('copy', { bubbles: true, cancelable: true, }) ); } else { const target = e.target.closest('.saved-id'); window.location.hash = `#/contact?floId=${target.dataset.floId}`; } }); function saveIdChanges() { const floID = getRef('edit_saved_id').value; let title = getRef('get_new_title').value.trim(); if (title == '') title = 'Unknown'; floGlobals.savedIds[floID] = { title } syncUserData('savedIds', floGlobals.savedIds).then(() => { render.savedIds() closePopup(); }).catch(error => { notify(error, 'error'); }) } function deleteSavedId() { getConfirmation('Do you want delete this FLO ID?', { confirmText: 'Delete', }).then(res => { if (res) { const toDelete = getRef('saved_ids_list').querySelector(`.saved-id[data-flo-id="${getRef('edit_saved_id').value}"]`); if (toDelete) toDelete.remove(); delete floGlobals.savedIds[getRef('edit_saved_id').value]; closePopup(); syncUserData('savedIds', floGlobals.savedIds).then(() => { notify(`Deleted saved ID`, 'success'); }).catch(error => { notify(error, 'error'); }); } }); } const savedIdsObserver = new MutationObserver((mutationList) => { mutationList.forEach(mutation => { conditionalClassToggle(getRef('saved_ids_tip'), 'hidden', !mutation.target.children.length); }) }) savedIdsObserver.observe(getRef('saved_ids_list'), { childList: true, }) function insertElementAlphabetically(name, elementToInsert) { const elementInserted = [...getRef('saved_ids_list').children].some(child => { const floID = child.dataset.floId; if (floGlobals.savedIds[floID].title.localeCompare(name) > 0) { child.before(elementToInsert) return true } }) if (!elementInserted) { getRef('saved_ids_list').append(elementToInsert) } } getRef('search_saved_ids_picker').addEventListener('input', debounce(async e => { const frag = document.createDocumentFragment() const searchKey = e.target.value.trim(); let allSavedIds = getArrayOfSavedIds(); if (searchKey !== '') { const fuse = new Fuse(allSavedIds, { keys: ['floID', 'details.title'] }) allSavedIds = fuse.search(searchKey).map(v => v.item) } renderElem(getRef('saved_ids_picker_list'), html`${allSavedIds.map(({ floID, details }) => render.savedIdPickerCard(floID, details))}`) if (searchKey !== '') { const potentialTarget = getRef('saved_ids_picker_list').firstElementChild if (potentialTarget) { potentialTarget.classList.add('highlight') } } }, 100)) getRef('search_saved_ids_picker').addEventListener('keydown', e => { if (e.key === 'Enter') { const potentialTarget = getRef('saved_ids_picker_list').firstElementChild if (potentialTarget) { potentialTarget.click() } } }) delegate(getRef('saved_ids_picker_list'), 'click', '.saved-id', e => { getRef('token_transfer__receiver').value = e.delegateTarget.dataset.floId getRef('token_transfer__receiver').focusIn() closePopup() }) let currentUserAction; function showTokenTransfer(type) { getRef('token_transfer__button').textContent = type; currentUserAction = type; if (type === 'send') { getRef('token_transfer__title').textContent = 'Send money to FLO ID'; } else { getRef('token_transfer__title').textContent = 'Request money from FLO ID'; } if (pagesData.lastPage === 'contact') { getRef('token_transfer__receiver').value = pagesData.params.floId; getRef('token_transfer__receiver').readOnly = true; getRef('token_transfer__receiver').querySelector('button').classList.add('hidden'); } else { getRef('token_transfer__receiver').readOnly = false; getRef('token_transfer__receiver').querySelector('button').classList.remove('hidden'); } openPopup('token_transfer_popup'); if (pagesData.lastPage === 'contact') { getRef('token_transfer__amount').focusIn(); } } userUI.sendMoneyToUser = function (floID, amount, remark) { getConfirmation('Confirm', { message: `Do you want to send ${amount} to ${getFloIdTitle(floID)}?`, confirmText: 'send' }).then(confirmation => { if (confirmation) { buttonLoader('token_transfer__button', true); User.sendToken(floID, amount, "|" + remark).then(txid => { console.warn(`Sent ${amount} to ${floID}`, txid); notify(`Sent ${amount} to ${getFloIdTitle(floID)}. It may take a few mins to reflect in their wallet`, 'success'); closePopup() }).catch(error => notify(error, 'error')) .finally(() => { buttonLoader('token_transfer__button', false); }) } }) } userUI.requestMoneyFromUser = function (floID, amount, remark) { getConfirmation('Confirm', { message: `Do you want to request ${amount} from ${getFloIdTitle(floID)}?`, confirmText: 'request' }).then(confirmation => { if (confirmation) { buttonLoader('token_transfer__button', true); User.requestToken(floID, amount, remark).then(result => { console.log(`Requested ${amount} from ${floID}`, result); notify(`Requested ${amount} from ${getFloIdTitle(floID)}`, 'success'); closePopup() }).catch(error => notify(error, 'error')) .finally(() => { buttonLoader('token_transfer__button', false); }) } }) } function executeUserAction() { const floID = getRef('token_transfer__receiver').value.trim(), amount = parseFloat(getRef('token_transfer__amount').value), remark = getRef('token_transfer__remark').value.trim(); if (currentUserAction === 'send') { userUI.sendMoneyToUser(floID, amount, remark); } else { userUI.requestMoneyFromUser(floID, amount, remark); } } function toggleFilters() { const animOptions = { duration: 200, easing: 'ease', fill: 'forwards', } if (getRef('history_applied_filters_wrapper').classList.contains('hidden') && getRef('history_applied_filters').children.length > 0) { getRef('history_applied_filters_wrapper').classList.remove('hidden') const filtersContainerDimensions = getRef('history_applied_filters_wrapper').getBoundingClientRect(); getRef('history_applied_filters_wrapper').animate([ { transform: `translateY(-1.5rem)`, opacity: 0 }, { transform: `translateY(0)`, opacity: 1 }, ], animOptions) getRef('payments_history').animate([ { transform: `translateY(-${filtersContainerDimensions.height}px)` }, { transform: `translateY(0)` }, ], animOptions) } else if (!getRef('history_applied_filters_wrapper').classList.contains('hidden') && getRef('history_applied_filters').children.length === 0) { getRef('history_applied_filters_wrapper').animate([ { transform: `translateY(0)`, opacity: 1 }, { transform: `translateY(-1.5rem)`, opacity: 0 }, ], animOptions) .onfinish = () => { getRef('history_applied_filters_wrapper').classList.add('hidden') } const filtersContainerDimensions = getRef('history_applied_filters_wrapper').getBoundingClientRect(); const historyDimensions = getRef('payments_history').getBoundingClientRect(); getRef('payments_history').animate([ { transform: `translateY(0)` }, { transform: `translateY(-${historyDimensions.top - filtersContainerDimensions.top}px)` }, ], animOptions).onfinish = (e) => { e.target.commitStyles() e.target.cancel() getRef('payments_history').style.transform = ''; } getRef('payments_type_filter').querySelector('input[value="all"]').checked = true; } } function applyPaymentsFilters() { const filter = getRef('payments_type_filter').querySelector('input:checked').value; if (filter !== 'all') { renderElem(getRef('history_applied_filters'), html` `); } toggleFilters() if (pagesData.params.asset == 'rupee') { render.rupeeHistory() } else { render.btcHistory() } closePopup() } function resetPaymentsFilters() { getRef('payments_type_filter').querySelector('input[value="all"]').checked = true; if (pagesData.params.asset == 'rupee') { render.rupeeHistory() } else { render.btcHistory() } closePopup() toggleFilters() } delegate(getRef('history_applied_filters'), 'click', '.applied-filter', e => { // const filter = e.delegateTarget.dataset.filter // const filterValue = e.delegateTarget.dataset.value e.delegateTarget.remove() if (pagesData.params.asset == 'rupee') { render.rupeeHistory() } else { render.btcHistory() } toggleFilters() }) function changeUpi() { const upiId = getRef('upi_id').value.trim(); Cashier.updateUPI(upiId).then(() => { getRef('my_upi_id').classList.remove('hidden') getRef('my_upi_id').value = upiId; getRef('change_upi_button').textContent = 'Change UPI ID'; notify('UPI ID updated successfully', 'success'); closePopup() }).catch(err => { notify(err, 'error'); }); } function getSignedIn(passwordType) { return new Promise((resolve, reject) => { try { console.log(floDapps.user.id) getPromptInput('Enter password', '', { isPassword: true, }).then(password => { if (password) { resolve(password) } }) }catch(err) { if (passwordType === 'PIN/Password') { floGlobals.isPrivKeySecured = true; getRef('private_key_field').removeAttribute('data-private-key'); getRef('private_key_field').setAttribute('placeholder', 'Password'); getRef('private_key_field').customValidation = null getRef('secure_pwd_button').closest('.card').classList.add('hidden'); } else { floGlobals.isPrivKeySecured = false; getRef('private_key_field').dataset.privateKey = '' getRef('private_key_field').setAttribute('placeholder', 'FLO private key'); getRef('private_key_field').customValidation = floCrypto.getPubKeyHex; getRef('secure_pwd_button').closest('.card').classList.remove('hidden'); } if (window.location.hash.includes('sign_in') || window.location.hash.includes('sign_up')) { showPage(window.location.hash); } else { location.hash = `#/sign_in`; } getRef('sign_in_button').onclick = () => { resolve(getRef('private_key_field').value.trim()); getRef('private_key_field').value = ''; showPage('loading'); }; getRef('sign_up_button').onclick = () => { resolve(getRef('generated_private_key').value.trim()); getRef('generated_private_key').value = ''; showPage('loading'); }; } }); } function setSecurePassword() { if (!floGlobals.isPrivKeySecured) { const password = getRef('secure_pwd_input').value.trim(); floDapps.securePrivKey(password).then(() => { floGlobals.isPrivKeySecured = true; notify('Password set successfully', 'success'); getRef('secure_pwd_button').closest('.card').classList.add('hidden'); closePopup(); }).catch(err => { notify(err, 'error'); }) } } function signOut() { getConfirmation('Sign out?', 'You are about to sign out of the app, continue?', 'Stay', 'Leave') .then(async (res) => { if (res) { await floDapps.clearCredentials(); location.reload(); } }); } function getAddressDetails(address) { return new Promise((resolve, reject) => { btc_api.getAddressData(address).then(data => { console.debug(data); let details = {}; details.balance = data.balance; details.address = data.address; details.txs = data.txs.map(tx => { let d = { txid: tx.txid, time: tx.time, block: tx.block_no } if (tx.outgoing) { d.type = "out"; d.amount = 0; d.receiver = new Set(); let change = 0; tx.outgoing.outputs.forEach(o => { if (o.address !== address) { d.receiver.add(o.address) d.amount += parseFloat(o.value) } else change += parseFloat(o.value) }); d.receiver = Array.from(d.receiver); d.amount = parseFloat(d.amount.toFixed(8)) d.fee = parseFloat((tx.outgoing.value - (d.amount + change)).toFixed(8)) if (!d.amount && change > 0) { d.type = "self"; d.amount = change delete d.receiver; d.address = address; } } else if (tx.incoming) { d.type = "in"; d.amount = parseFloat(tx.incoming.value); d.sender = Array.from(new Set(tx.incoming.inputs.map(i => i.address))); } return d; }) resolve(details); }).catch(error => reject(error)) }) } function calculateBtcFees() { fetch('https://bitcoiner.live/api/fees/estimates/latest').then(res => res.json()).then(data => { const satPerByte = data.estimates['60'].sat_per_vbyte; const legacyBytes = 200; const segwitBytes = 77; const fees = (legacyBytes * satPerByte + (0.25 * satPerByte) * segwitBytes) / Math.pow(10, 8); getRef('send_fee').value = fees.toFixed(8); }).catch(e => { console.error(e) }) } function togglePrivateKeyVisibility(input) { const target = input.closest('sm-input') target.type = target.type === 'password' ? 'text' : 'password'; target.focusIn() } const txParticipantsObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.type === 'childList') { if (mutation.addedNodes.length > 0 && mutation.target.children.length > 1) { const removeButton = mutation.target.firstElementChild.querySelector('.remove-card') if (!removeButton) { const newRemoveButton = html.node` ` mutation.target.firstElementChild.querySelector('.remove-card-wrapper').appendChild(newRemoveButton) } } else if (mutation.removedNodes.length > 0 && mutation.target.children.length === 1) { const removeButton = mutation.target.firstElementChild.querySelector('.remove-card') if (removeButton) { removeButton.remove() } } } }) }) txParticipantsObserver.observe(getRef('receiver_container'), { childList: true }) let globalExchangeRate = {} async function getExchangeRate() { return new Promise((resolve, reject) => { Promise.all(['usd', 'inr'].map(cur => fetch(`https://bitpay.com/api/rates/btc/${cur}`))).then(responses => { Promise.all(responses.map(res => res.json())).then(rates => { rates.forEach(rate => { globalExchangeRate[rate.code.toLowerCase()] = rate.rate }) globalExchangeRate.btc = 1 resolve(globalExchangeRate) }).catch(err => console.log(err)) }).catch(err => console.log(err)) }) } getRef('add_receiver').onclick = evt => { let receiverCard = getRef('receiver_template').content.cloneNode(true) if (!getRef('receiver_container').children.length) { receiverCard.querySelector('.remove-card').remove() } receiverCard.querySelector('.currency-symbol').innerHTML = ` ` getRef('receiver_container').appendChild(receiverCard); getRef('receiver_container').querySelectorAll('sm-input[data-btc-address]').forEach(input => input.customValidation = btc_api.validateAddress) } delegate(getRef('receiver_container'), 'click', '.remove-card', e => { e.target.closest('.receiver-card').remove() }) getRef('fees_selector').addEventListener('change', e => { if (e.target.value !== 'custom') { getRef('send_fee').readOnly = true; } switch (e.target.value) { case 'custom': getRef('send_fee').readOnly = false; getRef('send_fee').focusIn(); getRef('selected_fee_tip').textContent = 'Set custom fee'; break; case 'suggested': getRef('selected_fee_tip').textContent = 'Estimated time of confirmation is 1hr' calculateBtcFees(); break; } }) getRef('send_transaction').onclick = evt => { buttonLoader('send_transaction', true) floGlobals.user.private.then(privateKey => { const privKeys = btc_api.convert.wif(privateKey); const senders = btc_api.convert.legacy2bech(floDapps.user.id); const receivers = [...getRef('receiver_container').querySelectorAll('.receiver-input')].map(input => input.value.trim()); const amounts = [...getRef('receiver_container').querySelectorAll('.amount-input')].map(input => { return parseFloat(input.value.trim()) }); const fee = parseFloat(getRef('send_fee').value.trim()); console.debug(senders, receivers, amounts, fee); btc_api.sendTx(senders, privKeys, receivers, amounts, fee).then(result => { console.log(result); closePopup(); getRef('txid').value = result.txid; openPopup('txid_popup'); getRef('send_tx').reset() getExchangeRate().then(() => { calculateBtcFees() }).catch(e => { console.error(e) }) }).catch(error => { notify(`Error sending transaction \n ${error}`, 'error'); }).finally(_ => { buttonLoader('send_transaction', false) }) }).catch(error => { console.log(error); notify('Invalid password', 'error'); closePopup(); return false; }) } function convertAsset() { buttonLoader('convert_asset_button', true) const fromAsset = getRef('from_asset_selector').value; const fromAmount = parseFloat(getRef('from_amount').value.trim()); if (fromAsset === 'BTC') { btc_api.getBalance(btc_api.convert.legacy2bech(floDapps.user.id)).then(btcBalance => { if (btcBalance < fromAmount) { notify('You do not have enough BTC to convert', 'error'); buttonLoader('convert_asset_button', false) return; } }) } else { floTokenAPI.getBalance(floDapps.user.id).then((balance = 0) => { if(balance < fromAmount) { notify('You do not have enough rupee tokens to convert', 'error'); buttonLoader('convert_asset_button', false) return; } }) } // use api to convert asset }