347 lines
82 KiB
HTML
347 lines
82 KiB
HTML
<!doctype html> <html lang="en"> <head> <title>Bitcoin Wallet</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <link rel="shortcut icon" href="favicon.svg" type="image/x-icon"> <link rel="stylesheet" href="css/main.min.css"> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap" rel="stylesheet"> </head> <body class="hidden"> <sm-popup id="confirmation_popup"> <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> <article id="loading_page"> <strong>Getting Bitcoin Wallet ready</strong> </article> <div id="main_card"> <header id="main_header" class="flex align-center space-between"> <div class="app-brand"> <img src="favicon.svg" alt="Bitcoin Web Wallet logo" class="icon"> <div class="app-name"> <div class="app-name__company">RanchiMall</div> <h4 class="app-name__title"> Bitcoin Wallet </h4> </div> </div> <div class="flex align-center gap-0-3"> <sm-select id="currency_selector" class="margin-right-0-5"> <sm-option value="btc">BTC</sm-option> <sm-option value="inr">INR</sm-option> <sm-option value="usd">USD</sm-option> </sm-select> </div> </header> <main id="pages_container" class="grid" data-scrollable> <div id="check_details" class="page hidden" data-sm-containment> <section class="flex gap-0-5 margin-bottom-1-5"> <button id="gen_new_addr_btn" class="button primary-action interact"> <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> <g> <path d="M18.32,4.26C16.84,3.05,15.01,2.25,13,2.05v2.02c1.46,0.18,2.79,0.76,3.9,1.62L18.32,4.26z M19.93,11h2.02 c-0.2-2.01-1-3.84-2.21-5.32L18.31,7.1C19.17,8.21,19.75,9.54,19.93,11z M18.31,16.9l1.43,1.43c1.21-1.48,2.01-3.32,2.21-5.32 h-2.02C19.75,14.46,19.17,15.79,18.31,16.9z M13,19.93v2.02c2.01-0.2,3.84-1,5.32-2.21l-1.43-1.43 C15.79,19.17,14.46,19.75,13,19.93z M13,12V7h-2v5H7l5,5l5-5H13z M11,19.93v2.02c-5.05-0.5-9-4.76-9-9.95s3.95-9.45,9-9.95v2.02 C7.05,4.56,4,7.92,4,12S7.05,19.44,11,19.93z"/> </g> </g> </svg> Generate BTC address </button> <button id="retrieve_addr_btn" class="button primary-action interact" onclick='openPopup("retrieve_btc_addr_popup")'> <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="M14 12c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm-2-9c-4.97 0-9 4.03-9 9H0l4 4 4-4H5c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.51 0-2.91-.49-4.06-1.3l-1.42 1.44C8.04 20.3 9.94 21 12 21c4.97 0 9-4.03 9-9s-4.03-9-9-9z"/> </svg> Retrieve BTC address </button> </section> <section> <sm-form class="flex margin-bottom-2" style="--gap:0.5rem"> <sm-input type="search" id="search_query_input" placeholder="Search BTC address or transaction ID" required> <svg slot="icon" 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="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> <button id="check_address_button" class="button button--primary cta" style="height:3.2rem" type="submit" disabled>Search</button> </sm-form> <div id="address_details" class="hidden"> <div id="address_balance_card" class="grid gap-1 hidden"> <div class="flex"> <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 d="M21 7.28V5c0-1.1-.9-2-2-2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-2.28c.59-.35 1-.98 1-1.72V9c0-.74-.41-1.37-1-1.72zM20 9v6h-7V9h7zM5 19V5h14v2h-6c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h6v2H5z"/> <circle cx="16" cy="12" r="1.5"/> </svg> Balance </div> </div> <div class="flex flex-direction-column margin-bottom-1 sticky top-0 gap-0-5" style="background-color:rgba(var(--foreground-color),1);z-index:2"> <div class="flex align-center gap-0-5 space-between"> <h4>Transactions</h4> <sm-chips id="filter_selector"> <sm-chip value="all" selected>All</sm-chip> <sm-chip value="sent">Sent</sm-chip> <sm-chip value="received">Received</sm-chip> </sm-chips> </div> <sm-switch id="show_current_value" class="margin-left-auto hidden" onchange="handleValuationTypeChange(event)"> <p slot="left" class="margin-right-0-5"> Show current value </p> </sm-switch> </div> <div class="empty-state align-self-center text-center">Balance and transactions will appear here </div> </div> </section> </div> <div id="send" class="page hidden"> <sm-form id="send_tx" skip-submit> <div class="margin-bottom-0-5"> <div class="flex align-center space-between margin-bottom-0-5"> <h3>Senders</h3> <button class="button button--small" id="check_balance" onclick="checkBalance()">Check Balance</button> </div> <div class="flex align-center balance-wrapper"> <span>Total balance:</span> </div> <button id="add_sender" class="button--small" onclick="addSenderInput()"> <svg class="icon margin-right-0-5" 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 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/> </svg> Add sender </button> </div> <div> <h3 class="margin-bottom-0-5">Receivers</h3> <button id="add_receiver" class="button--small" onclick="addReceiverInput()"> <svg class="icon margin-right-0-5" 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 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/> </svg> Add receiver</button> </div> <div id="fees_section" class="grid gap-0-5"> <div class="flex align-center space-between"> <h4>Fees</h4> <sm-chips id="fees_selector"> <sm-chip value="suggested" selected>Suggested</sm-chip> <sm-chip value="custom">Custom</sm-chip> </sm-chips> </div> </div> <div class="multi-state-button margin-bottom-1-5"> <button id="send_transaction" type="submit" class="button button--primary cta w-100" disabled>Send</button> </div> </sm-form> </div> <div id="convert" class="page hidden flex flex-direction-column gap-1-5" data-sm-containment> <sm-chips id="conversion_view_selector" onchange="handleConversionRouteChange(event)"> <sm-chip value="flo" selected>FLO</sm-chip> <sm-chip value="btc">BTC</sm-chip> <sm-chip value="eth">ETH</sm-chip> </sm-chips> </div> </main> <nav id="main_navbar"> <ul id="menu"> <li> <a href="#/check_details" class="nav-item interactive"> <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="M12 5.69l5 4.5V18h-2v-6H9v6H7v-7.81l5-4.5M12 3L2 12h3v8h6v-6h2v6h6v-8h3L12 3z"/> </svg> <span class="nav-item__title"> Address </span> </a> </li> <li> <a href="#/send" class="nav-item interactive"> <svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <path d="M0 0h24v24H0V0z" fill="none"/> <path d="M4.01 6.03l7.51 3.22-7.52-1 .01-2.22m7.5 8.72L4 17.97v-2.22l7.51-1M2.01 3L2 10l15 2-15 2 .01 7L23 12 2.01 3z"/> </svg> <span class="nav-item__title"> Send </span> </a> </li> <li> <a href="#/convert" class="nav-item interactive"> <svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> </svg> <span class="nav-item__title"> Convert </span> </a> </li> </ul> </nav> </div> <sm-popup id="generate_btc_addr_popup"> <header slot="header" class="popup__header"> <div class="flex align-center"> <button class="popup__header__close" onclick="closePopup()"> <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> </header> <div class="grid gap-2"> <div id="flo_id_warning" class="grid justify-center gap-0-5"> <svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <path d="M0 0h24v24H0z" fill="none"/> <path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/> </svg> <h3>Keep your keys safe!</h3> <strong>Don't share with anyone. The private key cannot be recovered if lost.</strong> </div> </div> </sm-popup> <sm-popup id="retrieve_btc_addr_popup"> <header slot="header" class="popup__header"> <div class="flex align-center"> <button class="popup__header__close" onclick="closePopup()"> <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> </header> <section class="grid gap-1-5"> <div class="grid gap-0-5"> <h4>Did you forget your BTC address?</h4> <p>If you have your private key, enter it here and recover your BTC address.</p> </div> <sm-form> <div id="recovered_btc_addr_wrapper" class="hidden"> <h5>Recovered BTC address</h5> </div> <sm-input id="retrieve_btc_addr_field" type="password" placeholder="Private key" class="password-field" data-private-key required autofocus> <label slot="right" class="interact"> <input type="checkbox" class="hidden" autocomplete="off" readonly onchange="togglePrivateKeyVisibility(this)"> <svg class="icon invisible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Hide password</title> <path d="M0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0z" fill="none"/> <path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"/> </svg> <svg class="icon visible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Show password</title> <path d="M0 0h24v24H0z" fill="none"/> <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/> </svg> </label> </sm-input> <button class="button button--primary cta" type="submit" onclick="retrieveBtcAddr()">Recover</button> </sm-form> </section> </sm-popup> <sm-popup id="increase_fee_popup"> <header slot="header" class="popup__header"> <div class="flex align-center"> <button class="popup__header__close" onclick="closePopup()"> <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> <h3>Increase fee</h3> </div> </header> </sm-popup> <sm-popup id="transaction_result_popup"> <header slot="header" class="popup__header"> <div class="flex align-center"> <button class="popup__header__close" onclick="closePopup()"> <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> </header> </sm-popup> <template id="sender_template"> <fieldset class="sender-card card"> <sm-input class="priv-key-input password-field" type="password" placeholder="Private Key" animate required> <svg class="icon" slot="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="M21,10h-8.35C11.83,7.67,9.61,6,7,6c-3.31,0-6,2.69-6,6s2.69,6,6,6c2.61,0,4.83-1.67,5.65-4H13l2,2l2-2l2,2l4-4.04L21,10z M7,15c-1.65,0-3-1.35-3-3c0-1.65,1.35-3,3-3s3,1.35,3,3C10,13.65,8.65,15,7,15z"/> </g> </svg> <label slot="right" class="interact"> <input type="checkbox" class="hidden" autocomplete="off" readonly onchange="togglePrivateKeyVisibility(this)"> <svg class="icon invisible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Hide password</title> <path d="M0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0z" fill="none"/> <path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"/> </svg> <svg class="icon visible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Show password</title> <path d="M0 0h24v24H0z" fill="none"/> <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/> </svg> </label> </sm-input> <div class="flex align-center space-between full-bleed remove-card-wrapper"> <div class="flex align-center"> <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 d="M21 7.28V5c0-1.1-.9-2-2-2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-2.28c.59-.35 1-.98 1-1.72V9c0-.74-.41-1.37-1-1.72zM20 9v6h-7V9h7zM5 19V5h14v2h-6c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h6v2H5z"/> <circle cx="16" cy="12" r="1.5"/> </svg> <output class="sender-balance amount-shown flex align-center">Balance</output> </div> <button class="remove-card button--small"> <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 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"/> </svg> Remove </button> </div> </fieldset> </template> <template id="receiver_template"> <fieldset class="card receiver-card"> <div class="flex align-content-start gap-0-5 remove-card-wrapper"> <sm-input type="number" class="amount-input amount-shown" placeholder="Amount" min="0.000006" step="0.00000001" animate required> </sm-input> <button class="remove-card button--small"> <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 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"/> </svg> Remove </button> </div> </fieldset> </template> <script src="https://unpkg.com/uhtml@3.0.1/es.js"></script> <script src="scripts/components.min.js"></script> <script type="text/javascript" src="scripts/lib.js"></script> <script src="scripts/floCrypto.js"></script> <script type="text/javascript" src="scripts/btcOperator.js"></script> <script src="scripts/keccak.js"></script> <script src="scripts/floEthereum.js"></script> <script id="ui_utils">const uiGlobals={},{html:html,svg:svg,render:renderElem}=uhtml;function getRef(e){return document.getElementById(e)}uiGlobals.connectionErrorNotification=[],navigator.onLine||uiGlobals.connectionErrorNotification.push(notify("There seems to be a problem connecting to the internet, Please check you internet connection.","error")),window.addEventListener("offline",(()=>{uiGlobals.connectionErrorNotification.push(notify("There seems to be a problem connecting to the internet, Please check you internet connection.","error"))})),window.addEventListener("online",(()=>{uiGlobals.connectionErrorNotification.forEach((e=>{getRef("notification_drawer").remove(e)})),notify("We are back online.","success")}));const getConfirmation=(e,t={})=>new Promise((n=>{const{message:i="",cancelText:r="Cancel",confirmText:a="OK",danger:s=!1}=t;openPopup("confirmation_popup",!0),getRef("confirm_title").innerText=e,renderElem(getRef("confirm_message"),i);const o=getRef("confirmation_popup").querySelector(".cancel-button"),c=getRef("confirmation_popup").querySelector(".confirm-button");c.textContent=a,o.textContent=r,s?c.classList.add("button--danger"):c.classList.remove("button--danger"),c.onclick=()=>{closePopup(),n(!0)},o.onclick=()=>{closePopup(),n(!1)}})),debounce=(e,t)=>{let n=null;return(...i)=>{window.clearTimeout(n),n=window.setTimeout((()=>{e.apply(null,i)}),t)}};function addClass(e,t){e.forEach((e=>{document.querySelector(e).classList.add(t)}))}function removeClass(e,t){e.forEach((e=>{document.querySelector(e).classList.remove(t)}))}function getAllElements(e){return Array.from(document.querySelectorAll(e))}let zIndex=50;function openPopup(e,t){return zIndex++,getRef(e).setAttribute("style",`z-index: ${zIndex}`),getRef(e).show({pinned:t}),getRef(e)}function closePopup(){void 0!==popupStack.peek()&&popupStack.peek().popup.hide()}function notify(e,t,n={}){let i;switch(t){case"success":i='<svg class="icon icon--success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z"/></svg>';break;case"error":i='<svg class="icon icon--error" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z"/></svg>',n.pinned=!0}return"error"===t&&console.error(e),getRef("notification_drawer").push(e,{icon:i,...n})}function getFormattedTime(e,t){try{String(e).length<13&&(e*=1e3);let[n,i,r,a]=new Date(e).toString().split(" "),s=new Date(e).getMinutes(),o=new Date(e).getHours();(new Date).toString().split(" ");s=s<10?`0${s}`:s;let c="";switch(c=o>12?`${o-12}:${s}`:0===o?`12:${s}`:`${o}:${s}`,c=o>=12?`${c} PM`:`${c} AM`,t){case"date-only":return`${i} ${r}, ${a}`;case"time-only":return c;case"relative":return Date.now()-new Date(e)<864e5?`${c}`:relativeTime.from(e);default:return`${i} ${r} ${a}, ${c}`}}catch(t){return console.error(t),e}}function detectBrowser(){let e,t=navigator.userAgent,n=t.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i)||[];return/trident/i.test(n[1])?(e=/\brv[ :]+(\d+)/g.exec(t)||[],"IE "+(e[1]||"")):"Chrome"===n[1]&&(e=t.match(/\b(OPR|Edge)\/(\d+)/),null!=e)?e.slice(1).join(" ").replace("OPR","Opera"):(n=n[2]?[n[1],n[2]]:[navigator.appName,navigator.appVersion,"-?"],null!=(e=t.match(/version\/(\d+)/i))&&n.splice(1,1,e[1]),n.join(" "))}document.addEventListener("popupopened",(e=>{e.target.id})),document.addEventListener("popupclosed",(e=>{switch(zIndex--,e.target.id){case"retrieve_btc_addr_popup":getRef("recovered_btc_addr_wrapper").classList.add("hidden");break;case"increase_fee_popup":renderElem(getRef("increase_fee_popup_content"),html``)}})),window.addEventListener("hashchange",(e=>routeTo(window.location.hash)));let selectedCurrency="btc",historicPriceApis={active:0,list:["https://utility-api.ranchimall.net"]};function createRipple(e,t){const n=document.createElement("span"),i=Math.max(t.clientWidth,t.clientHeight),r=i/2,a=t.getBoundingClientRect();n.style.width=n.style.height=`${i}px`,n.style.left=e.clientX-(a.left+r)+"px",n.style.top=e.clientY-(a.top+r)+"px",n.classList.add("ripple");const s=n.animate([{opacity:1,transform:"scale(0)"},{transform:"scale(4)",opacity:0}],{duration:600,fill:"forwards",easing:"ease-out"});t.append(n),s.onfinish=()=>{n.remove()}}window.addEventListener("load",(()=>{const[e,t]=detectBrowser().split(" "),n={Chrome:85,Firefox:75,Safari:13};e in n?parseInt(t)<n[e]&¬ify(`${e} ${t} is not fully supported, some features may not work properly. Please update to ${n[e]} or higher.`,"error"):notify("Browser is not fully compatible, some features may not work. for best experience please use Chrome, Edge, Firefox or Safari","error"),document.body.classList.remove("hidden"),document.addEventListener("keyup",(e=>{"Escape"===e.key&&closePopup()})),document.addEventListener("copy",(()=>{notify("copied","success")})),document.addEventListener("pointerdown",(e=>{e.target.closest("button:not(:disabled), .interactive:not(:disabled)")&&createRipple(e,e.target.closest("button, .interactive"))})),btcOperator.checkIfTor().then((e=>{e&&historicPriceApis.list.push("http://omwkzk6bd6zuragdqsrhdyzgxzre7yx4vzrou4vzftintzc2dmagp6qd.onion:8257")})),getExchangeRate().then((()=>{setTimeout((()=>{document.getElementById("currency_selector").value=selectedCurrency,getRef("show_current_value")&&(document.getElementById("show_current_value").checked=showCurrentValue,getRef("show_current_value").classList.toggle("hidden","btc"===selectedCurrency))}),100),showCurrentValue="true"===localStorage.getItem("btc-wallet-show-current-value")||!1})).catch((e=>{console.error(e),getRef("show_current_value").remove(),showCurrentValue=!1})).finally((()=>{selectedCurrency=localStorage.getItem("btc-wallet-currency")||"btc",routeTo(window.location.hash),setTimeout((()=>{getRef("loading_page").animate([{transform:"translateY(0)"},{transform:"translateY(-100%)"}],{duration:300,fill:"forwards",easing:"ease"}).onfinish=()=>{getRef("loading_page").remove()}}),500),addSenderInput(),addReceiverInput(),document.querySelectorAll(".currency-symbol").forEach((e=>e.innerHTML=currencyIcons[selectedCurrency]))}))}));const pagesData={params:{}};let tempData;async function routeTo(e,t={}){const{firstLoad:n,hashChange:i,isPreview:r}=t;let a,s,o={};if(""===e)a="check_details";else if(e.includes("/")){let t;[t,s]=e.split("?"),[,a,subPageId1]=t.split("/")}else a=e;if(s){const e=new URLSearchParams("?"+s);o=Object.fromEntries(e.entries())}switch(o&&(pagesData.params=o),a){case"check_details":if(o.query){const e=getRef("search_query_input").value.trim();o.query!==e&&(getRef("search_query_input").value=o.query),render.queryResult(o.query),o.currency||"btc"===selectedCurrency||location.hash.includes(selectedCurrency)||history.replaceState(null,null,`${location.hash}¤cy=${selectedCurrency}`),o.currency&&o.currency!==selectedCurrency&&(selectedCurrency=o.currency,document.getElementById("currency_selector").value=selectedCurrency,document.querySelectorAll(".currency-symbol").forEach((e=>e.innerHTML=currencyIcons[selectedCurrency])),getRef("show_current_value")&&getRef("show_current_value").classList.toggle("hidden","btc"===selectedCurrency))}break;case"convert":renderConversionPage(o.from)}const c={duration:100,fill:"forwards"};let l=getRef("main_navbar").querySelector(".nav-item--active");const d=document.querySelector(`.nav-item[href="#/${a}"]`);if(d){getRef("main_navbar").classList.contains("hidden")&&(getRef("main_navbar").classList.remove("hide-away"),getRef("main_navbar").classList.remove("hidden"),getRef("main_navbar").animate([{transform:isMobileView?"translateY(100%)":"translateX(-100%)",opacity:0},{transform:"none",opacity:1}],{duration:100,fill:"forwards",easing:"ease"})),getRef("main_header").classList.remove("hidden");const e=[...getRef("main_navbar").querySelectorAll(".nav-item")].indexOf(l),t=[...getRef("main_navbar").querySelectorAll(".nav-item")].indexOf(d),n=e<t,i=html.node`<div class="nav-item__indicator"></div>`;let r=getRef("main_navbar").querySelector(".nav-item__indicator");if(r){if(t!==e){const e=r.getBoundingClientRect(),t=d.getBoundingClientRect();let a;a=isMobileView?(t.width-e.width)/2+e.width:(t.height-e.height)/2+e.height,indicatorObserver.observe(r),r.animate([{transform:"none",opacity:1},{transform:`translate${isMobileView?"X":"Y"}(${n?`${a}px`:`-${a}px`})`,opacity:0}],{...c,easing:"ease-in"}).onfinish=()=>{r.remove()},tempData={currentActiveElement:d,currentIndicator:i,isOnTop:n,animOptions:c,moveBy:a}}}else r=i.cloneNode(!0),l=d,l.append(r);l.classList.remove("nav-item--active"),d.classList.add("nav-item--active")}else getRef("main_navbar").classList.contains("hidden")||(getRef("main_navbar").classList.add("hide-away"),getRef("main_navbar").animate([{transform:"none",opacity:1},{transform:isMobileView?"translateY(100%)":"translateX(-100%)",opacity:0}],{duration:200,fill:"forwards",easing:"ease"}).onfinish=()=>{getRef("main_navbar").classList.add("hidden")},getRef("main_header").classList.add("hidden"));pagesData.lastPage!==a&&(closePopup(),document.querySelectorAll(".page").forEach((e=>e.classList.add("hidden"))),getRef(a).classList.remove("hidden"),getRef(a).animate([{opacity:0},{opacity:1}],{duration:300,fill:"forwards",easing:"ease"}),pagesData.lastPage=a)}const indicatorObserver=new IntersectionObserver((e=>{e.forEach((e=>{if(!e.isIntersecting){const{currentActiveElement:e,currentIndicator:t,isOnTop:n,animOptions:i,moveBy:r}=tempData;e.append(t),t.animate([{transform:`translate${isMobileView?"X":"Y"}(${n?`-${r}px`:`${r}px`})`,opacity:0},{transform:"none",opacity:1}],{...i,easing:"ease-out"})}}))}),{threshold:1});class LazyLoader{constructor(e,t,n,i={}){const{batchSize:r=10,freshRender:a,bottomFirst:s=!1,domUpdated:o}=i;this.elementsToRender=t,this.arrayOfElements="function"==typeof t?this.elementsToRender():t||[],this.renderFn=n,this.intersectionObserver,this.batchSize=r,this.freshRender=a,this.domUpdated=o,this.bottomFirst=s,this.shouldLazyLoad=!1,this.lastScrollTop=0,this.lastScrollHeight=0,this.lazyContainer=document.querySelector(e),this.update=this.update.bind(this),this.render=this.render.bind(this),this.init=this.init.bind(this),this.clear=this.clear.bind(this)}get elements(){return this.arrayOfElements}init(){this.intersectionObserver=new IntersectionObserver(((e,t)=>{e.forEach((e=>{e.isIntersecting&&(t.disconnect(),this.render({lazyLoad:!0}))}))})),this.mutationObserver=new MutationObserver((e=>{e.forEach((e=>{"childList"===e.type&&e.addedNodes.length&&(this.bottomFirst?this.lazyContainer.firstElementChild&&this.intersectionObserver.observe(this.lazyContainer.firstElementChild):this.lazyContainer.lastElementChild&&this.intersectionObserver.observe(this.lazyContainer.lastElementChild))}))})),this.mutationObserver.observe(this.lazyContainer,{childList:!0}),this.render()}update(e){this.arrayOfElements="function"==typeof e?this.elementsToRender():e||[]}render(e={}){let{lazyLoad:t=!1}=e;this.shouldLazyLoad=t;const n=document.createDocumentFragment();t?this.bottomFirst?(this.updateEndIndex=this.updateStartIndex,this.updateStartIndex=this.updateEndIndex-this.batchSize):(this.updateStartIndex=this.updateEndIndex,this.updateEndIndex=this.updateEndIndex+this.batchSize):(this.intersectionObserver.disconnect(),this.bottomFirst?(this.updateEndIndex=this.arrayOfElements.length,this.updateStartIndex=this.updateEndIndex-this.batchSize-1):(this.updateStartIndex=0,this.updateEndIndex=this.batchSize),this.lazyContainer.innerHTML=""),this.lastScrollHeight=this.lazyContainer.scrollHeight,this.lastScrollTop=this.lazyContainer.scrollTop,this.arrayOfElements.slice(this.updateStartIndex,this.updateEndIndex).forEach(((e,t)=>{n.append(this.renderFn(e))})),this.bottomFirst?(this.lazyContainer.prepend(n),this.lastScrollTop+=this.lazyContainer.scrollHeight-this.lastScrollHeight,this.lazyContainer.scrollTo({top:this.lastScrollTop}),this.lastScrollHeight=this.lazyContainer.scrollHeight):this.lazyContainer.append(n),!t&&this.bottomFirst&&(this.lazyContainer.scrollTop=this.lazyContainer.scrollHeight),!t&&this.freshRender&&this.freshRender()}clear(){this.intersectionObserver.disconnect(),this.mutationObserver.disconnect(),this.lazyContainer.innerHTML=""}reset(){this.arrayOfElements="function"==typeof this.elementsToRender?this.elementsToRender():this.elementsToRender||[],this.render()}}function buttonLoader(e,t){const n="string"==typeof e?document.getElementById(e):e;if(!n)return;n.dataset.hasOwnProperty("wasDisabled")||(n.dataset.wasDisabled=n.disabled);const i={duration:200,fill:"forwards",easing:"ease"};if(t)n.disabled=!0,n.parentNode.append(document.createElement("sm-spinner")),n.animate([{clipPath:"circle(100%)"},{clipPath:"circle(0)"}],i);else{n.disabled="true"===n.dataset.wasDisabled,n.animate([{clipPath:"circle(0)"},{clipPath:"circle(100%)"}],i).onfinish=e=>{n.removeAttribute("data-original-state")};const e=n.parentNode.querySelector("sm-spinner");e&&e.remove()}}let isMobileView=!1;const mobileQuery=window.matchMedia("(max-width: 40rem)");function handleMobileChange(e){isMobileView=e.matches}mobileQuery.addEventListener("change",handleMobileChange),handleMobileChange(mobileQuery);const slideInLeft=[{opacity:0,transform:"translateX(1.5rem)"},{opacity:1,transform:"translateX(0)"}],slideOutLeft=[{opacity:1,transform:"translateX(0)"},{opacity:0,transform:"translateX(-1.5rem)"}],slideInRight=[{opacity:0,transform:"translateX(-1.5rem)"},{opacity:1,transform:"translateX(0)"}],slideOutRight=[{opacity:1,transform:"translateX(0)"},{opacity:0,transform:"translateX(1.5rem)"}],slideInDown=[{opacity:0,transform:"translateY(-1.5rem)"},{opacity:1,transform:"translateY(0)"}],slideOutUp=[{opacity:1,transform:"translateY(0)"},{opacity:0,transform:"translateY(-1.5rem)"}];function showChildElement(e,t,n={}){return new Promise((i=>{const{mobileView:r=!1,entry:a,exit:s}=n,o={duration:150,easing:"ease",fill:"forwards"},c="string"==typeof e?document.getElementById(e):e,l=[...c.children].find((e=>!e.classList.contains(r?"hide-on-mobile":"hidden")));l!==c.children[t]&&(l.getAnimations().forEach((e=>e.cancel())),c.children[t].getAnimations().forEach((e=>e.cancel())),l?s?l.animate(s,o).onfinish=()=>{l.classList.add(r?"hide-on-mobile":"hidden"),c.children[t].classList.remove(r?"hide-on-mobile":"hidden"),a&&(c.children[t].animate(a,o).onfinish=()=>i())}:(l.classList.add(r?"hide-on-mobile":"hidden"),c.children[t].classList.remove(r?"hide-on-mobile":"hidden"),i()):(c.children[t].classList.remove(r?"hide-on-mobile":"hidden"),c.children[t].animate(a,o).onfinish=()=>i()))}))}</script> <script>function toYDM(e){const t=new Date(e);return`${t.getFullYear()}-${t.getMonth()+1}-${t.getDate()}`}window.smCompConfig={"sm-input":[{selector:"[data-flo-address]",customValidation:e=>e?{isValid:floCrypto.validateFloID(e),errorText:'Invalid FLO address.<br> It usually starts with "F"'}:{isValid:!1,errorText:"Please enter a FLO address"}},{selector:"[data-btc-address]",customValidation:e=>e?{isValid:btcOperator.validateAddress(e),errorText:'Invalid address.<br> It usually starts with "1", "3" or "bc1"'}:{isValid:!1,errorText:"Please enter a BTC address"}},{selector:".sender-input",customValidation:e=>{if(!e)return{isValid:!1,errorText:"Please enter a BTC address"};const t=btcOperator.validateAddress(e);return{isValid:!(!1===t||"bech32m"===t),errorText:"bech32m"===t?"This is a Taproot address. This wallet does't support claiming Taproot funds yet.":"Please enter valid BTC address"}}},{selector:"[data-private-key]",customValidation:(e,t)=>{if(!e)return{isValid:!1,errorText:"Please enter a private key"};if(floCrypto.getPubKeyHex(e)){const r=t.dataset.forAddress;return r?{isValid:btcOperator.verifyKey(r,e),errorText:`This private key does not match the address ${r}`}:{isValid:!0}}return{isValid:!1,errorText:"Invalid private key. Please check and try again."}}},{selector:".amount-input",customValidation:e=>{if(!e)return{isValid:!1,errorText:"Please enter an amount"};const t={btc:6e-6,inr:roundUp(6e-6*globalExchangeRate.inr,2),usd:roundUp(6e-6*globalExchangeRate.usd,2)};return{isValid:parseFloat(e)>=t[selectedCurrency],errorText:`Amount must be greater than ${getConvertedAmount(t.btc,{shouldFormatAmount:!0})} ${selectedCurrency.toUpperCase()}`}}}]};let transactionsLazyLoader,txDetailsAbortController,showCurrentValue=!1;function handleValuationTypeChange(e){showCurrentValue=e.target.checked,document.querySelectorAll("transaction-card").forEach((e=>e.render())),localStorage.setItem("btc-wallet-show-current-value",showCurrentValue)}class TransactionCard extends HTMLElement{render=e=>{e&&(this.transactionDetails=e);let t,r,{address:s,amount:a,time:n,txid:i,sender:l,receiver:o,type:c,block:d,historicPrice:u}=this.transactionDetails;const h=o||l||[],p=h.slice(0,2).map((e=>html`<a href="${`#/check_details?query=${e}`}" class="tx-participant wrap-around">${e}</a>`)),v=d<0||null==d;"out"===c?(t=html`Sent to ${p}`,r=svg`<svg class="icon sent" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>`):"in"===c?(t=html`Received from ${p}`,r=svg`<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="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"/></svg>`):"self"===c&&(t="Sent to self",r=svg`<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 8-4 4h3c0 3.31-2.69 6-6 6-1.01 0-1.97-.25-2.8-.7l-1.46 1.46C8.97 19.54 10.43 20 12 20c4.42 0 8-3.58 8-8h3l-4-4zM6 12c0-3.31 2.69-6 6-6 1.01 0 1.97.25 2.8.7l1.46-1.46C15.03 4.46 13.57 4 12 4c-4.42 0-8 3.58-8 8H1l4 4 4-4H6z"/></svg>`),v&&(r=svg`<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="M6,2l0.01,6L10,12l-3.99,4.01L6,22h12v-6l-4-4l4-3.99V2H6z M16,16.5V20H8v-3.5l4-4L16,16.5z"/></g></svg>`);pagesData.params?.query||getRef("search_query_input").value.trim();const g="out"===c||"self"===c,m=`transaction grid ${c} ${v?"unconfirmed-tx":""}`,f=(u&&u[selectedCurrency]&&(getConvertedAmount(a),getConvertedAmount(a,{onDate:n})),{shouldFormatAmount:!0});"btc"!==selectedCurrency&&!showCurrentValue&&u&&u[selectedCurrency]&&(f.onDate=n),renderElem(this,html`
|
|
<li class="${m}" .dataset=${{txid:i,transactingAddresses:h.slice(2),currency:selectedCurrency}}>
|
|
<div class="transaction__icon">${r}</div>
|
|
<div class="grid gap-0-5">
|
|
<div class="flex gap-1 space-between">
|
|
${n?html`
|
|
<time class="transaction__time">${getFormattedTime(n)}</time>
|
|
`:""}
|
|
<div class="transaction__amount">${getConvertedAmount(a,f)}</div>
|
|
</div>
|
|
<div class="transaction__receiver">
|
|
${t}
|
|
${h.length>2?html`<button onclick=${showAllAddresses} class="button button--small show-more" title="See all addresses">... +${h.length-2} more</button>`:""}
|
|
</div>
|
|
<div class="flex gap-0-5 flex-wrap align-center">
|
|
<a class="button button--small gap-0-3 align-center button--colored transaction__id" href="${`#/check_details?query=${i}`}">
|
|
<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="M11 7h2v2h-2zm0 4h2v6h-2zm1-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"/></svg>
|
|
View details
|
|
</a>
|
|
${g&&!d?html`
|
|
<div class="multi-state-button">
|
|
<button class="button button--small gap-0-3" onclick=${e=>initFeeChange(e,i)} title="Resend transaction with greater fees to reduce confirmation time">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>
|
|
Increase fee
|
|
</button>
|
|
</div>
|
|
`:""}
|
|
</div>
|
|
${v?html`
|
|
<p class="pending-badge">
|
|
${"out"===c?"\n Confirmation pending: amount will be deducted after transaction is confirmed.\n ":"\n Confirmation pending: balance will be updated after transaction is confirmed.\n "}
|
|
${g&&!d?" Try increasing fee to speed up confirmation":""}
|
|
</p>
|
|
`:""}
|
|
</div>
|
|
</li>
|
|
`)}}async function getHistoricPrice(e=[]){try{if(!e.length)return[];let t;Array.isArray(e)||(e=[e]);const r=e.filter((e=>!mappedHistoricPrices.has(e))),s=new AbortController,{signal:a}=s;if(r.length){t=setTimeout((()=>{throw s.abort(),new Error("Historic price data is taking longer than usual to load. Historic prices won't be shown")}),8e3);const r=await fetch(`${historicPriceApis.list[historicPriceApis.active]}/price-history?dates=${e.join()}`,{signal:a}).then((e=>e.json()));clearTimeout(t),r.forEach((e=>{mappedHistoricPrices.set(toYDM(e.date),{usd:e.usd,inr:e.inr})}))}return e.map((e=>mappedHistoricPrices.get(e)))}catch(t){return historicPriceApis.active<historicPriceApis.list.length-1?(historicPriceApis.active++,getHistoricPrice(e)):(showCurrentValue=!0,localStorage.setItem("btc-wallet-show-current-value",showCurrentValue),getRef("show_current_value")&&(getRef("show_current_value").checked=showCurrentValue),notify("Could not fetch historic price data. Showing current value instead","error"),[])}}window.customElements.define("transaction-card",TransactionCard);const mappedHistoricPrices=new Map,render={transactionCard(e){const t=document.createElement("transaction-card");return t.render(e),t},async transactions(e){try{getRef("address_details").classList.remove("hidden"),getRef("transactions_list").innerHTML='<sm-spinner class="justify-self-center margin-top-1-5"></sm-spinner>',getRef("address_balance").innerHTML='<sm-spinner class="justify-self-center margin-top-1-5"></sm-spinner>';const{txs:t,balance:r}=await btcOperator.getAddressData(e);if(getRef("address_balance").value=getConvertedAmount(r,{shouldFormatAmount:!0}),getRef("address_balance").dataset.btcAmount=r,getRef("address_balance").parentElement.classList.remove("hidden"),getRef("filter_selector").classList.remove("hidden"),t.length){const e=t.map((e=>toYDM(e.time)));await getHistoricPrice(e),t.forEach((e=>{const t=mappedHistoricPrices.get(toYDM(e.time));t&&(e.historicPrice=t)}));let r=t;const s=getRef("filter_selector").value;"all"!==s&&(r=r.filter((e=>"sent"===s?"out"===e.type:"in"===e.type))),transactionsLazyLoader?transactionsLazyLoader.update(r):transactionsLazyLoader=new LazyLoader("#transactions_list",r,render.transactionCard),transactionsLazyLoader.init(),getRef("transactions_list").previousElementSibling.classList.remove("hidden")}else getRef("transactions_list").textContent="No transactions found"}catch(e){notify(e,"error"),getRef("filter_selector").classList.add("hidden"),getRef("transactions_list").textContent="The data service is temporarily unavailable due to over-usage. Please try again in an hour."}finally{getRef("check_address_button").disabled=!1}},addressDetails(e){getRef("check_address_button").disabled=!0,render.transactions(e)},async txDetails(e){getRef("tx_details").classList.remove("hidden"),renderElem(getRef("tx_details"),html`<sm-spinner class="justify-self-center margin-top-1-5"></sm-spinner>`),txDetailsAbortController&&txDetailsAbortController.abort(),txDetailsAbortController=new AbortController,btcOperator.getTx(e).then((async t=>{const{block:r,time:s,size:a,fee:n,inputs:i,outputs:l,confirmations:o=0,total_input_value:c,total_output_value:d}=t,u=r<0||null==r;let h=0;if(l.length>1){let e=2===l.length&&l[0].address===l[1].address;h=l.reduce(((t,{address:r,value:s})=>!e&&i.find((e=>e.address===r))?t:t+s),0)}else h=l[0].value;let p={shouldFormatAmount:!0},v={};"btc"!==selectedCurrency&&(mappedHistoricPrices.get(toYDM(s))||await getHistoricPrice(toYDM(s)),!showCurrentValue&&"btc"!==selectedCurrency&&mappedHistoricPrices.has(toYDM(s))&&(p.onDate=s,v.onDate=s)),renderElem(getRef("tx_details"),html`
|
|
<div id="tx_details__header" class="flex align-center gap-1 flex-wrap">
|
|
<h3>Transaction Details</h3>
|
|
<div class="flex align-center gap-1 space-between flex-1">
|
|
${s?html`
|
|
<time>${getFormattedTime(s)}</time>
|
|
`:""}
|
|
${u?html` <h4 id="tx_status">
|
|
<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="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/><path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z"/></svg>
|
|
Unconfirmed
|
|
</h4> `:""}
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-direction-column">
|
|
<p>Amount</p>
|
|
<div id="tx_amount" class="amount-shown" .dataset=${{btcAmount:h,...v}}>${getConvertedAmount(h,p)}</div>
|
|
</div>
|
|
${u?html`
|
|
<div class="flex flex-direction-column gap-0-5" style="padding: 1rem;border-radius:0.5rem; border: solid thin rgba(var(--text-color),0.3); background-color: rgba(var(--text-color),0.02)">
|
|
<h3>
|
|
Taking too long to confirm?
|
|
</h3>
|
|
<p>
|
|
You can increase the fee to speed up confirmation.
|
|
</p>
|
|
<div class="multi-state-button margin-right-auto">
|
|
<button class="button gap-0-3 button--primary" onclick=${t=>initFeeChange(t,e)} title="Resend transaction with greater fees to reduce confirmation time">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>
|
|
Increase fee
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`:""}
|
|
<div id="tx_technicals" class="justify-self-center details-wrapper">
|
|
<div class="tx-detail">
|
|
<div class="flex align-center gap-0-3">
|
|
<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="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg>
|
|
<div>Confirmations</div>
|
|
</div>
|
|
<div style="font-size: 1.5rem">${o}</div>
|
|
</div>
|
|
${u?"":html`
|
|
<div class="tx-detail">
|
|
<div class="flex align-center gap-0-3">
|
|
<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><g><g><path d="M3,3v8h8V3H3z M9,9H5V5h4V9z M3,13v8h8v-8H3z M9,19H5v-4h4V19z M13,3v8h8V3H13z M19,9h-4V5h4V9z M13,13v8h8v-8H13z M19,19h-4v-4h4V19z"/></g></g></g></svg>
|
|
<div>Block</div>
|
|
</div>
|
|
<div>${r}</div>
|
|
</div>
|
|
`}
|
|
<div class="tx-detail">
|
|
<div class="flex align-center gap-0-3">
|
|
<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="M15 4c-4.42 0-8 3.58-8 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6zM3 12c0-2.61 1.67-4.83 4-5.65V4.26C3.55 5.15 1 8.27 1 12s2.55 6.85 6 7.74v-2.09c-2.33-.82-4-3.04-4-5.65z"/></svg>
|
|
<div>Fee</div>
|
|
</div>
|
|
<div class="amount-shown" .dataset=${{btcAmount:n,...v}}>${getConvertedAmount(n,p)}</div>
|
|
</div>
|
|
</div>
|
|
<details class="margin-bottom-1-5 justify-self-center w-100">
|
|
<summary>
|
|
More details
|
|
<svg class="icon down-arrow" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M24 24H0V0h24v24z" fill="none" opacity=".87"/><path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6-1.41-1.41z"/></svg>
|
|
</summary>
|
|
<div class="details-wrapper">
|
|
<div class="tx-detail">
|
|
<div class="flex align-center gap-0-3">
|
|
<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="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"/></svg>
|
|
<div>Total Inputs</div>
|
|
</div>
|
|
<div class="amount-shown" .dataset=${{btcAmount:c,...v}}>${getConvertedAmount(c,p)}</div>
|
|
</div>
|
|
<div class="tx-detail">
|
|
<div class="flex align-center gap-0-3">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>
|
|
<div>Total Outputs</div>
|
|
</div>
|
|
<div class="amount-shown" .dataset=${{btcAmount:d,...v}}>${getConvertedAmount(d,p)}</div>
|
|
</div>
|
|
<div class="tx-detail">
|
|
<div class="flex align-center gap-0-3">
|
|
<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="M2 20h20v-4H2v4zm2-3h2v2H4v-2zM2 4v4h20V4H2zm4 3H4V5h2v2zm-4 7h20v-4H2v4zm2-3h2v2H4v-2z"/></svg>
|
|
<div>Size</div>
|
|
</div>
|
|
<div>${a} bytes</div>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
<div id="in_out_wrapper" class="flex flex-wrap">
|
|
<div>
|
|
<div class="flex align-center space-between margin-bottom-1" style="padding: 0.5rem 1rem;">
|
|
<b>Sender addresses</b>
|
|
<b>Amount</b>
|
|
</div>
|
|
<ul>
|
|
${i.map((e=>html`
|
|
<li class="in-out-card">
|
|
<a href="${`#/check_details?query=${e.address}`}" class="input-address wrap-around">${e.address}</a>
|
|
<div class="input-value amount-shown" .dataset=${{btcAmount:e.value,...v}}>${getConvertedAmount(e.value,p)}</div>
|
|
</li>
|
|
`))}
|
|
</ul>
|
|
</div>
|
|
<div>
|
|
<div class="flex align-center space-between margin-bottom-1" style="padding: 0.5rem 1rem;">
|
|
<b>Receiver addresses</b>
|
|
<b>Amount</b>
|
|
</div>
|
|
<ul>
|
|
${l.map((e=>html`
|
|
<li class="in-out-card">
|
|
<a href="${`#/check_details?query=${e.address}`}" class="output-address wrap-around">${e.address}</a>
|
|
<div class="output-value amount-shown" .dataset=${{btcAmount:e.value,...v}}>${getConvertedAmount(e.value,p)}</div>
|
|
</li>
|
|
`))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
`)})).catch((e=>{notify("Invalid transaction ID","error"),renderElem(getRef("tx_details"),html``)}))},queryResult(e){const t=checkQueryStringType(e);"address"===t?(getRef("tx_details").classList.add("hidden"),render.addressDetails(e)):"txid"===t?(getRef("address_details").classList.add("hidden"),render.txDetails(e),transactionsLazyLoader&&(transactionsLazyLoader.clear(),transactionsLazyLoader=null)):(transactionsLazyLoader&&(transactionsLazyLoader.clear(),transactionsLazyLoader=null),getRef("address_details").classList.add("hidden"),getRef("tx_details").classList.add("hidden"),notify("Invalid address or transaction id","error"))}},currencyIcons={btc:' <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"></rect> </g> <g> <path d="M17.06,11.57C17.65,10.88,18,9.98,18,9c0-1.86-1.27-3.43-3-3.87L15,3h-2v2h-2V3H9v2H6v2h2v10H6v2h3v2h2v-2h2v2h2v-2 c2.21,0,4-1.79,4-4C19,13.55,18.22,12.27,17.06,11.57z M10,7h4c1.1,0,2,0.9,2,2s-0.9,2-2,2h-4V7z M15,17h-5v-4h5c1.1,0,2,0.9,2,2 S16.1,17,15,17z"> </path> </g> </svg> ',usd:'<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="M11.8 10.9c-2.27-.59-3-1.2-3-2.15 0-1.09 1.01-1.85 2.7-1.85 1.78 0 2.44.85 2.5 2.1h2.21c-.07-1.72-1.12-3.3-3.21-3.81V3h-3v2.16c-1.94.42-3.5 1.68-3.5 3.61 0 2.31 1.91 3.46 4.7 4.13 2.5.6 3 1.48 3 2.41 0 .69-.49 1.79-2.7 1.79-2.06 0-2.87-.92-2.98-2.1h-2.2c.12 2.19 1.76 3.42 3.68 3.83V21h3v-2.15c1.95-.37 3.5-1.5 3.5-3.55 0-2.84-2.43-3.81-4.7-4.4z"/></svg>',inr:'<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><g><path d="M13.66,7C13.1,5.82,11.9,5,10.5,5L6,5V3h12v2l-3.26,0c0.48,0.58,0.84,1.26,1.05,2L18,7v2l-2.02,0c-0.25,2.8-2.61,5-5.48,5 H9.77l6.73,7h-2.77L7,14v-2h3.5c1.76,0,3.22-1.3,3.46-3L6,9V7L13.66,7z"/></g></g></svg>'};function formatAmount(e=0,t=selectedCurrency){return"string"==typeof e&&(e=parseFloat(e)),e?e.toLocaleString(void 0,{style:"currency",currency:t,minimumFractionDigits:0,maximumFractionDigits:"btc"===selectedCurrency?8:2}):"0"}let globalExchangeRate={};async function getExchangeRate(){return new Promise(((e,t)=>{Promise.all(["usd","inr"].map((e=>fetch(`https://bitpay.com/api/rates/btc/${e}`)))).then((r=>{Promise.all(r.map((e=>e.json()))).then((t=>{t.forEach((e=>{globalExchangeRate[e.code.toLowerCase()]=e.rate})),globalExchangeRate.btc=1,e(globalExchangeRate)})).catch((e=>t(e)))})).catch((e=>t(e)))}))}function getConvertedAmount(e,{shouldFormatAmount:t=!1,onDate:r}={}){"string"==typeof e&&(e=parseFloat(e));let s=e;if(r&&"btc"!==selectedCurrency){const t=mappedHistoricPrices.get(toYDM(r));s=t?parseFloat((e*t[selectedCurrency]).toFixed(8)):parseFloat((e*globalExchangeRate[selectedCurrency]).toFixed(8))}else globalExchangeRate[selectedCurrency]&&(s=parseFloat((e*globalExchangeRate[selectedCurrency]).toFixed(8)));return t&&(s=formatAmount(s)),s}function roundUp(e,t=2){return parseFloat((Math.ceil(e*Math.pow(10,t))/Math.pow(10,t)).toFixed(t))}let previouslySelectedCurrency=localStorage.getItem("btc-wallet-currency")||"btc";function retrieveBtcAddr(){function e(){let e=getRef("retrieve_btc_addr_field").value.trim();getRef("recovered_btc_addr_wrapper").classList.remove("hidden"),getRef("recovered_btc_addr").value=btcOperator.bech32Address(e)}document.startViewTransition?document.startViewTransition((()=>{e()})):e()}function togglePrivateKeyVisibility(e){const t=e.closest("sm-input");t.type="password"===t.type?"text":"password",t.focusIn()}function handleConversionRouteChange(e){location.hash=`#/convert?from=${e.target.value}`}function renderConversionPage(e="flo"){let t="",r=null,s="";switch(getRef("conversion_view_selector").value=e,e){case"flo":t="Convert FLO private key to corresponding BTC | ETH address & private key",r="Convert FLO address to BTC address",s=html`<sm-input id="address_for_conversion" placeholder="FLO Address" data-flo-address animate required></sm-input>`;break;case"btc":t="Convert BTC private key to corresponding FLO | ETH address & private key",r="Convert BTC address to FLO address",s=html`<sm-input id="address_for_conversion" placeholder="BTC Address" data-btc-address animate required></sm-input>`;break;case"eth":t="Convert ETH private key to corresponding FLO | BTC address & private key"}renderElem(getRef("key_conversion_content"),html.for(getRef("key_conversion_content"),e)`
|
|
<div class="grid gap-1-5">
|
|
<div class="grid gap-0-3">
|
|
<h3>Private key converter</h3>
|
|
<p>${t}</p>
|
|
</div>
|
|
<sm-form oninvalid=${()=>renderElem(getRef("private_key_conversion_result"),html``)}>
|
|
<div class="input-action-wrapper">
|
|
<sm-input type="password" id="private_key_for_conversion" class="password-field" placeholder=${`${e.toUpperCase()} private key`} data-private-key required animate>
|
|
<label slot="right" class="interact">
|
|
<input type="checkbox" class="hidden" autocomplete="off" readonly onchange="togglePrivateKeyVisibility(this)">
|
|
<svg class="icon invisible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Hide password</title> <path d="M0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0z" fill="none" /> <path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z" /> </svg>
|
|
<svg class="icon visible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Show password</title> <path d="M0 0h24v24H0z" fill="none" /> <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" /> </svg>
|
|
</label>
|
|
</sm-input>
|
|
<button type="submit" onclick=${()=>convertPrivateKey(e)} class="button button--primary cta" disabled>Convert</button>
|
|
</div>
|
|
</sm-form>
|
|
<ul id="private_key_conversion_result" class="grid gap-0-5"></ul>
|
|
</div>
|
|
${r?html`
|
|
<div class="grid gap-1-5">
|
|
<div class="grid gap-0-3">
|
|
<h3>Address converter</h3>
|
|
<p class="panel-footer">${r}</p>
|
|
</div>
|
|
<sm-form class="flex" oninvalid=${()=>renderElem(getRef("address_conversion_result"),html``)}>
|
|
<div class="input-action-wrapper">
|
|
${s}
|
|
<button class="button--primary justify-self-center cta" onclick=${()=>convertAddress(e)} type="submit" disabled>
|
|
Convert
|
|
</button>
|
|
</div>
|
|
</sm-form>
|
|
<div id="address_conversion_result" class="converted-card"></div>
|
|
</div>
|
|
`:""}
|
|
`)}function convertPrivateKey(e){let t,r,s,a,n,i,l=getRef("private_key_for_conversion").value.trim();/^[0-9a-fA-F]{64}$/.test(l)&&(l=coinjs.privkey2wif(l));try{switch(e){case"flo":s=btcOperator.convert.wif(l),a=btcOperator.bech32Address(s),n=coinjs.wif2privkey(l).privkey,i=floEthereum.ethAddressFromPrivateKey(n);break;case"btc":t=btcOperator.convert.wif(l,bitjs.priv),r=floCrypto.getFloID(t),n=coinjs.wif2privkey(l).privkey,i=floEthereum.ethAddressFromPrivateKey(n);break;case"eth":s=btcOperator.convert.wif(l),a=btcOperator.bech32Address(s),t=l,r=floCrypto.getFloID(t)}renderElem(getRef("private_key_conversion_result"),html`
|
|
${t?html`
|
|
<li class="grid gap-1 converted-card">
|
|
<div class="grid">
|
|
<span class="label">FLO Address</span>
|
|
<sm-copy value="${r}"></sm-copy>
|
|
</div>
|
|
<div class="grid">
|
|
<span class="label">FLO Private Key</span>
|
|
<sm-copy value="${t}"></sm-copy>
|
|
</div>
|
|
</li>
|
|
`:""}
|
|
${s?html`
|
|
<li class="grid gap-1 converted-card">
|
|
<div class="grid">
|
|
<span class="label">BTC Address</span>
|
|
<sm-copy value="${a}"></sm-copy>
|
|
</div>
|
|
<div class="grid">
|
|
<span class="label">BTC Private Key</span>
|
|
<sm-copy value="${s}"></sm-copy>
|
|
</div>
|
|
</li>
|
|
`:""}
|
|
${n?html`
|
|
<li class="grid gap-1 converted-card">
|
|
<div class="grid">
|
|
<span class="label">ETH Address</span>
|
|
<sm-copy value="${i}"></sm-copy>
|
|
</div>
|
|
<div class="grid">
|
|
<span class="label">ETH Private Key</span>
|
|
<sm-copy value="${n}"></sm-copy>
|
|
</div>
|
|
</li>
|
|
`:""}
|
|
`)}catch(e){notify("Invalid private key","error"),renderElem(getRef("private_key_conversion_result"),html``)}}function convertAddress(e){const t=getRef("address_for_conversion").value.trim();let r,s;try{switch(e){case"flo":r=btcOperator.convert.legacy2bech(t),s="BTC";break;case"btc":r=floCrypto.toFloID(t),s="FLO"}if(r===t)return void notify("Address is already converted","error");renderElem(getRef("address_conversion_result"),html`
|
|
<span class="label">${s} Address</span>
|
|
<sm-copy value="${r}"></sm-copy>
|
|
`)}catch(e){notify("Invalid address","error"),renderElem(getRef("address_conversion_result"),html``)}}getRef("currency_selector").addEventListener("change",(e=>{selectedCurrency=e.target.value,"check_details"===pagesData.lastPage&&(location.hash.includes("currency")?history.replaceState(null,null,`${location.hash.split("&")[0]}¤cy=${selectedCurrency}`):history.replaceState(null,null,`${location.hash}¤cy=${selectedCurrency}`)),getRef("show_current_value")&&getRef("show_current_value").classList.toggle("hidden","btc"===selectedCurrency),localStorage.setItem("btc-wallet-currency",selectedCurrency),document.querySelectorAll(".currency-symbol").forEach((e=>e.innerHTML=currencyIcons[selectedCurrency])),document.querySelectorAll("transaction-card").forEach((e=>e.render())),document.querySelectorAll(".amount-shown").forEach((e=>{if(e.tagName.includes("SM-")){const t=parseFloat(e.value.trim());let r;const s=globalExchangeRate.inr/globalExchangeRate.usd;switch(previouslySelectedCurrency){case"usd":r="inr"===selectedCurrency?roundUp(t*s):roundUp(t/globalExchangeRate.usd,8);break;case"inr":r="usd"===selectedCurrency?roundUp(t/s):roundUp(t/globalExchangeRate.inr,8);break;case"btc":r=roundUp(t*globalExchangeRate[selectedCurrency])}e.value=r,e.isValid}else{if(void 0===e.dataset.btcAmount)return;e.textContent=getConvertedAmount(e.dataset.btcAmount,{shouldFormatAmount:!0,onDate:parseInt(e.dataset.onDate)})}})),previouslySelectedCurrency=selectedCurrency})),getRef("filter_selector").addEventListener("change",(async e=>{const t=getRef("search_query_input").value;render.transactions(t)})),getRef("gen_new_addr_btn").addEventListener("click",(()=>{const{wif:e,address:t,segwitAddress:r,bech32Address:s}=btcOperator.newKeys;renderElem(getRef("generated_btc_addr"),html`
|
|
<div>
|
|
<h5>BTC Address</h5>
|
|
<sm-copy value="${s}"></sm-copy>
|
|
</div>
|
|
<div>
|
|
<h5>Private Key</h5>
|
|
<sm-copy value="${e}"></sm-copy>
|
|
</div>
|
|
`),openPopup("generate_btc_addr_popup")}));const txParticipantsObserver=new MutationObserver((e=>{e.forEach((e=>{if("childList"===e.type)if(e.addedNodes.length>0&&e.target.children.length>1){if(!e.target.firstElementChild.querySelector(".remove-card")){const t=html.node`
|
|
<button class="remove-card button--small">
|
|
<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>
|
|
`;e.target.firstElementChild.querySelector(".remove-card-wrapper").appendChild(t)}}else if(e.removedNodes.length>0&&1===e.target.children.length){const t=e.target.firstElementChild.querySelector(".remove-card");t&&t.remove()}}))}));function addSenderInput(e){let t=getRef("sender_template").content.cloneNode(!0);getRef("sender_container").children.length||t.querySelector(".remove-card").remove(),getRef("sender_container").appendChild(t),e&&(getRef("sender_container").lastElementChild.querySelector(".sender-input").value=e)}async function checkBalance(){const e=[...getRef("sender_container").querySelectorAll(".sender-input")].filter((e=>""!==e.value.trim())).map((e=>e.value.trim()));if(0===e.length)return void notify("Please add at least one sender address","error");getRef("total_balance").innerHTML="<sm-spinner></sm-spinner>";let t=0,r=[...getRef("sender_container").querySelectorAll(".sender-balance")];r.forEach((e=>e.innerHTML="<sm-spinner></sm-spinner>")),Promise.all(e.map(((e,t)=>btcOperator.getBalance(e)))).then((e=>{e.forEach(((e,s)=>{r[s].textContent=getConvertedAmount(e,{shouldFormatAmount:!0}),r[s].dataset.btcAmount=e,t+=e})),getRef("total_balance").textContent=`${getConvertedAmount(t,{shouldFormatAmount:!0})}`,getRef("total_balance").dataset.btcAmount=t})).catch((e=>{console.error(e),notify("Error while fetching balance","error"),r.forEach((e=>e.innerHTML="")),getRef("total_balance").innerHTML=""}))}function addReceiverInput(e,t){let r=getRef("receiver_template").content.cloneNode(!0);getRef("receiver_container").children.length||r.querySelector(".remove-card").remove(),r.querySelector(".currency-symbol")&&(r.querySelector(".currency-symbol").innerHTML=currencyIcons[selectedCurrency]),getRef("receiver_container").appendChild(r),e&&(getRef("receiver_container").lastElementChild.querySelector(".receiver-input").value=e),t&&(getRef("receiver_container").lastElementChild.querySelector(".amount-input").value=getConvertedAmount(t))}function renderFeesUI(){switch(getRef("fees_selector").value){case"custom":renderElem(getRef("fees_wrapper"),html`
|
|
<p id="selected_fee_tip">Set custom fee</p>
|
|
<sm-input type="number" id="send_fee" class="amount-shown" placeholder="Fee" min="0.000001" step="0.00000001"
|
|
error-text="Please enter valid fees" animate required>
|
|
<div class="currency-symbol flex" slot="icon"></div>
|
|
</sm-input>
|
|
`),document.getElementById("send_fee").focusIn(),getRef("fees_wrapper").querySelector(".currency-symbol").innerHTML=currencyIcons[selectedCurrency],getRef("fees_section").classList.remove("hidden"),renderElem(getRef("error_section"),html``);break;case"suggested":renderElem(getRef("fees_wrapper"),html`<sm-spinner></sm-spinner>`),getRef("send_tx").isFormValid?calculateExactFee():calculateApproxFee().then((e=>{renderElem(getRef("fees_wrapper"),html`
|
|
<div class="grid gap-0-3">
|
|
<div>
|
|
Approximate fee: <b id="recommended_fee" class="amount-shown" data-btc-amount=${e}>${getConvertedAmount(e,{shouldFormatAmount:!0})}</b>
|
|
</div>
|
|
<p style="opacity: 0.8;">*Exact fee will be calculated after you fill all the required fields</p>
|
|
</div>
|
|
`)})).catch((e=>{getRef("fees_selector").children[1].click(),getRef("fees_selector").classList.add("hidden")})),getRef("fees_section").classList.remove("hidden"),renderElem(getRef("error_section"),html``)}}function getTransactionInputs(){return[[...new Set([...getRef("sender_container").querySelectorAll(".sender-input")].map((e=>e.value.trim())))],[...getRef("sender_container").querySelectorAll(".priv-key-input")].map((e=>e.value.trim())),[...getRef("receiver_container").querySelectorAll(".receiver-input")].map((e=>e.value.trim())),[...getRef("receiver_container").querySelectorAll(".amount-input")].map((e=>parseFloat(e.value.trim())/(globalExchangeRate[selectedCurrency]||1)))]}function calculateApproxFee(){return new Promise(((e,t)=>{fetch("https://bitcoiner.live/api/fees/estimates/latest").then((r=>{r.json().then((t=>{const r=t.estimates[60].sat_per_vbyte;e((200*r+.25*r*77)/Math.pow(10,8))})).catch((e=>{t(e)}))})).catch((e=>{t(e)}))}))}function calculateExactFee(){return new Promise(((e,t)=>{renderElem(getRef("fees_wrapper"),html`<sm-spinner></sm-spinner>`);const[r,s,a,n]=getTransactionInputs();btcOperator.createTx(r,a,n).then((({fee:t})=>{renderElem(getRef("fees_wrapper"),html` <b id="recommended_fee" class="amount-shown" data-btc-amount=${t}>${getConvertedAmount(t,{shouldFormatAmount:!0})}</b> `),getRef("send_transaction").disabled=!1,getRef("fees_section").classList.remove("hidden"),getRef("error_section").classList.add("hidden"),e(t)})).catch((e=>{if(getRef("send_transaction").disabled=!0,getRef("fees_section").classList.add("hidden"),getRef("error_section").classList.remove("hidden"),console.error(e),e.includes("Invalid private key")){e.split(":")[1].split(",").map((e=>e.trim())).forEach((e=>{const t=[...getRef("sender_container").querySelectorAll(".sender-input")].find((t=>t.value.trim()===e));t&&t.nextElementSibling.showError()}))}else renderElem(getRef("error_section"),html`
|
|
<p id="selected_fee_tip" class="error flex align-center gap-0-5">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M11 15h2v2h-2v-2zm0-8h2v6h-2V7zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/></svg>
|
|
${e}
|
|
</p>
|
|
`),t(e)}))}))}function showTransactionResult(e,t){if("success"===e)renderElem(getRef("transaction_result_popup__content"),html`
|
|
<svg class="icon user-action-result__icon success" 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="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" /> </svg>
|
|
<div class="grid gap-0-5 justify-center text-center">
|
|
<h4>Transaction sent</h4>
|
|
<p>Confirmation of transaction might take few hours. </p>
|
|
</div>
|
|
<div class="grid">
|
|
<span class="label">Transaction ID</span>
|
|
<sm-copy value=${t}></sm-copy>
|
|
</div>
|
|
<a class="button button--primary" href=${`#/check_details?query=${t}`}>Check transaction status</a>
|
|
`);openPopup("transaction_result_popup")}function isTxId(e){return 64===e.length&&e.match(/^[0-9a-f]+$/i)}function checkQueryStringType(e){return btcOperator.validateAddress(e)||/^bc1[a-z0-9]{59}$/i.test(e)?"address":isTxId(e)?"txid":"invalid"}function showAllAddresses(e){const t=e.target.closest("li").dataset.transactingAddresses.split(",").map((e=>html.node`<a href="${`#/check_details?query=${e}`}" class="tx-participant wrap-around">${e}</a>`));e.target.closest("button").before(...t),e.target.closest("button").remove()}txParticipantsObserver.observe(getRef("sender_container"),{childList:!0}),txParticipantsObserver.observe(getRef("receiver_container"),{childList:!0}),getRef("send_tx").addEventListener("click",(e=>{e.target.closest(".remove-card")&&e.target.closest(".card").remove()})),getRef("fees_selector").addEventListener("change",renderFeesUI),getRef("send_tx").addEventListener("valid",debounce((e=>{"suggested"===getRef("fees_selector").value?calculateExactFee():(getRef("fees_section").classList.remove("hidden"),getRef("error_section").classList.add("hidden"),getRef("send_tx").isFormValid&&(getRef("send_transaction").disabled=!1))}),300)),getRef("send_tx").addEventListener("invalid",(e=>{renderFeesUI(),getRef("send_transaction").disabled=!0})),getRef("send_transaction").onclick=async e=>{buttonLoader("send_transaction",!0);const[t,r,s,a]=getTransactionInputs();let n=parseFloat(getRef("recommended_fee")?.dataset.btcAmount);if("custom"===getRef("fees_selector").value){const e=getRef("send_fee").value.trim();if(!e||isNaN(e)||e<=0)return notify("Please enter a valid fee","error"),void buttonLoader("send_transaction",!1);n=parseFloat((parseFloat(e)/(globalExchangeRate[selectedCurrency]||1)).toFixed(8))}await getConfirmation("Confirm Transaction",{message:html`
|
|
<div class="grid gap-1-5">
|
|
<div class="grid gap-0-5">
|
|
<h5>Senders</h5>
|
|
<ul class="flex flex-direction-column gap-0-5">
|
|
${t.map((e=>html`<li class="wrap-around">${e}</li>`))}
|
|
</ul>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<h5>Receivers</h5>
|
|
<ul class="flex flex-direction-column gap-0-5">
|
|
${s.map(((e,t)=>html`<li class="wrap-around flex flex-direction-column gap-0-5" style="padding:0.5rem;border:solid thin rgba(var(--text-color),0.3);border-radius: 0.3rem;">
|
|
${e} <span class="amount-shown">${getConvertedAmount(a[t],{shouldFormatAmount:!0})}</span>
|
|
</li>`))}
|
|
</ul>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<h5>Fee</h5>
|
|
<div class="flex wrap-around gap-0-5">
|
|
<span class="amount-shown">${getConvertedAmount(n,{shouldFormatAmount:!0})}</span>
|
|
</div>
|
|
</div>
|
|
</div>`,confirmText:"Confirm",cancelText:"Cancel"})?btcOperator.sendTx(t,r,s,a,n).then((e=>{console.log(e),showTransactionResult("success",e),getRef("send_tx").reset()})).catch((e=>{console.error(e),e.hasOwnProperty("hasInsufficientBalance")?notify("Insufficient balance","error"):"TypeError: Failed to fetch"===e?notify('\n <div style="display: grid; gap: 0.3rem;">\n <h4>Couldn\'t fetch transaction ID!</h4>\n <p style="font-size: 0.9rem; opacity: 0.8">Please check transaction history before retrying.</p>\n </div>\n ',"error",{action:{label:"Check History",callback:()=>{location.hash=`/check_details?query=${t[0]}`,document.getElementById("notification_drawer").clearAll()}}}):notify(`Error sending transaction \n ${e}`,"error")})).finally((e=>{buttonLoader("send_transaction",!1)})):buttonLoader("send_transaction",!1)},getRef("check_address_button").addEventListener("click",(e=>{const t=getRef("search_query_input").value.trim();pagesData.params.hasOwnProperty("query")&&t===pagesData.params.query?render.queryResult(t):location.hash=`#/check_details?query=${t}`}));let changingFeeOf=null;async function initFeeChange(e,t){const r=e.target.closest("button");buttonLoader(r,!0),changingFeeOf=t;try{const{inputs:e,outputs:r,fee:s}=await btcOperator.getTx(t),a=e.some((e=>["multisig","multisigBech32"].includes(btcOperator.validateAddress(e.address))));let n=[];if(a){const e=btcOperator.deserializeTx(await btcOperator.getTx.hex(t));n=btcOperator.extractLastHexStrings(e.witness).flatMap((e=>{const{address:t,required:r,pubKeys:s}=btcOperator.decodeRedeemScript(e)||{};return s.map((e=>btcOperator.bech32Address(e)))}))}else n=e.map((e=>e.address));const i=r.map((e=>e.address)),l=r.reduce(((e,{address:t,value:r})=>(e[t]?e[t]+=r:e[t]=r,e)),{}),o=r.map((e=>5e-8));let c=null;if(!a){const{fee:e}=await btcOperator.createTx(n,i,o,null,{allowUnconfirmedUtxos:!0});e>s&&(c=e)}renderElem(getRef("increase_fee_popup_content"),html`
|
|
<sm-form style="--gap: 2rem">
|
|
<div class="grid gap-0-5">
|
|
<h4>Senders</h4>
|
|
<ul class="grid gap-0-5">
|
|
${[...new Set(n)].map((e=>html.node`<li class="increase-fee-sender grid gap-1">
|
|
<div>
|
|
<div class="label">Address</div>
|
|
<b class="sender__address wrap-around">${e}</b>
|
|
</div>
|
|
<sm-input class="sender__private-key password-field" type="password" placeholder="Private Key" data-for-address=${e} data-private-key animate required>
|
|
<svg class="icon" slot="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"></rect> </g> <g> <path d="M21,10h-8.35C11.83,7.67,9.61,6,7,6c-3.31,0-6,2.69-6,6s2.69,6,6,6c2.61,0,4.83-1.67,5.65-4H13l2,2l2-2l2,2l4-4.04L21,10z M7,15c-1.65,0-3-1.35-3-3c0-1.65,1.35-3,3-3s3,1.35,3,3C10,13.65,8.65,15,7,15z"></path> </g> </svg>
|
|
<label slot="right" class="interact">
|
|
<input type="checkbox" class="hidden" autocomplete="off" readonly="" onchange="togglePrivateKeyVisibility(this)">
|
|
<svg class="icon invisible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Hide password</title> <path d="M0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0z" fill="none"></path> <path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"></path> </svg>
|
|
<svg class="icon visible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Show password</title> <path d="M0 0h24v24H0z" fill="none"></path> <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"></path> </svg>
|
|
</label>
|
|
</sm-input>
|
|
</li>`))}
|
|
</ul>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<h4>Receivers</h4>
|
|
<ul class="grid gap-0-5">
|
|
${Object.entries(l).map((([e,t])=>html.node`<li class="increase-fee-receiver grid gap-1">
|
|
<div>
|
|
<div class="label">Address</div>
|
|
<b class="wrap-around">${e}</b>
|
|
</div>
|
|
<div>
|
|
<div class="label">Amount</div>
|
|
<b>${getConvertedAmount(t,{shouldFormatAmount:!0})}</b>
|
|
</div>
|
|
</li>`))}
|
|
</ul>
|
|
</div>
|
|
<div class="grid gap-0-5">
|
|
<p>
|
|
Previous fee: <b>${getConvertedAmount(s,{shouldFormatAmount:!0})}</b> ${c?html`| Recommended fee: <b>${getConvertedAmount(c,{shouldFormatAmount:!0})}</b>`:""}
|
|
</p>
|
|
<sm-input id="new_fee" placeholder="New fee" type="number" min=${getConvertedAmount(s)} step="0.00000001" error-text=${`New fee should be greater than ${getConvertedAmount(s,{shouldFormatAmount:!0})}`} animate required>
|
|
<div class="currency-symbol flex" slot="icon"> </div>
|
|
</sm-input>
|
|
</div>
|
|
<div class="multi-state-button">
|
|
<button id="increase_fee" class="button button--primary" onclick=${()=>increaseFee(a)} type="submit">Increase fee</button>
|
|
</div>
|
|
</sm-form>
|
|
`),document.getElementById("new_fee").querySelector(".currency-symbol").innerHTML=currencyIcons[selectedCurrency],openPopup("increase_fee_popup")}catch(e){notify(e.message||e,"error")}finally{buttonLoader(r,!1)}}async function increaseFee(e=!1){buttonLoader(document.getElementById("increase_fee"),!0);const t=parseFloat((parseFloat(document.getElementById("new_fee").value.trim())/(globalExchangeRate[selectedCurrency]||1)).toFixed(8)),r=[];document.querySelectorAll(".increase-fee-sender").forEach((e=>{const t=e.querySelector(".sender__address").textContent.trim(),s=e.querySelector(".sender__private-key").value.trim();if(!btcOperator.verifyKey(t,s))return notify(`Invalid private key for address ${t}`,"error");s&&r.push(s)}));try{let s;s=e?await btcOperator.editFee_corewallet(changingFeeOf,t,r):await btcOperator.editFee(changingFeeOf,t,r),btcOperator.broadcastTx(s).then((e=>{console.log(e),closePopup(),showTransactionResult("success",e)})).catch((e=>{notify(e,"error")})).finally((e=>{buttonLoader(document.getElementById("increase_fee"),!1),changingFeeOf=null}))}catch(e){notify(e,"error"),buttonLoader(document.getElementById("increase_fee"),!1)}}</script> |