3476 lines
138 KiB
HTML
3476 lines
138 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Binance Smart Chain</title>
|
|
<link rel="stylesheet" href="css/main.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-notifications id="notification_drawer"></sm-notifications>
|
|
<sm-popup id="confirmation_popup">
|
|
<h4 id="confirm_title"></h4>
|
|
<p id="confirm_message" class="breakable"></p>
|
|
<div class="flex align-center gap-0-5 margin-left-auto">
|
|
<button class="button cancel-button">Cancel</button>
|
|
<button class="button button--primary confirm-button ok-button">
|
|
Ok
|
|
</button>
|
|
</div>
|
|
</sm-popup>
|
|
<main>
|
|
<header id="main_header">
|
|
<div id="logo" class="app-brand">
|
|
<svg id="main_logo" class="icon" viewBox="0 0 27.25 32">
|
|
<title>RanchiMall</title>
|
|
<path
|
|
d="M27.14,30.86c-.74-2.48-3-4.36-8.25-6.94a20,20,0,0,1-4.2-2.49,6,6,0,0,1-1.25-1.67,4,4,0,0,1,0-2.26c.37-1.08.79-1.57,3.89-4.55a11.66,11.66,0,0,0,3.34-4.67,6.54,6.54,0,0,0,.05-2.82C20,3.6,18.58,2,16.16.49c-.89-.56-1.29-.64-1.3-.24a3,3,0,0,1-.3.72l-.3.55L13.42.94C13,.62,12.4.26,12.19.15c-.4-.2-.73-.18-.72.05a9.39,9.39,0,0,1-.61,1.33s-.14,0-.27-.13C8.76.09,8-.27,8,.23A11.73,11.73,0,0,1,6.76,2.6C4.81,5.87,2.83,7.49.77,7.49c-.89,0-.88,0-.61,1,.22.85.33.92,1.09.69A5.29,5.29,0,0,0,3,8.33c.23-.17.45-.29.49-.26a2,2,0,0,1,.22.63A1.31,1.31,0,0,0,4,9.34a5.62,5.62,0,0,0,2.27-.87L7,8l.13.55c.19.74.32.82,1,.65a7.06,7.06,0,0,0,3.46-2.47l.6-.71-.06.64c-.17,1.63-1.3,3.42-3.39,5.42L6.73,14c-3.21,3.06-3,5.59.6,8a46.77,46.77,0,0,0,4.6,2.41c.28.13,1,.52,1.59.87,3.31,2,4.95,3.92,4.95,5.93a2.49,2.49,0,0,0,.07.77h0c.09.09,0,.1.9-.14a2.61,2.61,0,0,0,.83-.32,3.69,3.69,0,0,0-.55-1.83A11.14,11.14,0,0,0,17,26.81a35.7,35.7,0,0,0-5.1-2.91C9.37,22.64,8.38,22,7.52,21.17a3.53,3.53,0,0,1-1.18-2.48c0-1.38.71-2.58,2.5-4.23,2.84-2.6,3.92-3.91,4.67-5.65a3.64,3.64,0,0,0,.42-2A3.37,3.37,0,0,0,13.61,5l-.32-.74.29-.48c.17-.27.37-.63.46-.8l.15-.3.44.64a5.92,5.92,0,0,1,1,2.81,5.86,5.86,0,0,1-.42,1.94c0,.12-.12.3-.15.4a9.49,9.49,0,0,1-.67,1.1,28,28,0,0,1-4,4.29C8.62,15.49,8.05,16.44,8,17.78a3.28,3.28,0,0,0,1.11,2.76c.95,1,2.07,1.74,5.25,3.32,3.64,1.82,5.22,2.9,6.41,4.38A4.78,4.78,0,0,1,21.94,31a3.21,3.21,0,0,0,.14.92,1.06,1.06,0,0,0,.43-.05l.83-.22.46-.12-.06-.46c-.21-1.53-1.62-3.25-3.94-4.8a37.57,37.57,0,0,0-5.22-2.82A13.36,13.36,0,0,1,11,21.19a3.36,3.36,0,0,1-.8-4.19c.41-.85.83-1.31,3.77-4.15,2.39-2.31,3.43-4.13,3.43-6a5.85,5.85,0,0,0-2.08-4.29c-.23-.21-.44-.43-.65-.65A2.5,2.5,0,0,1,15.27.69a10.6,10.6,0,0,1,2.91,2.78A4.16,4.16,0,0,1,19,6.16a4.91,4.91,0,0,1-.87,3c-.71,1.22-1.26,1.82-4.27,4.67a9.47,9.47,0,0,0-2.07,2.6,2.76,2.76,0,0,0-.33,1.54,2.76,2.76,0,0,0,.29,1.47c.57,1.21,2.23,2.55,4.65,3.73a32.41,32.41,0,0,1,5.82,3.24c2.16,1.6,3.2,3.16,3.2,4.8a1.94,1.94,0,0,0,.09.76,4.54,4.54,0,0,0,1.66-.4C27.29,31.42,27.29,31.37,27.14,30.86ZM6.1,7h0a3.77,3.77,0,0,1-1.46.45L4,7.51l.68-.83a25.09,25.09,0,0,0,3-4.82A12,12,0,0,1,8.28.76c.11-.12.77.32,1.53,1l.63.58-.57.84A10.34,10.34,0,0,1,6.1,7Zm5.71-1.78A9.77,9.77,0,0,1,9.24,7.18h0a5.25,5.25,0,0,1-1.17.28l-.58,0,.65-.78a21.29,21.29,0,0,0,2.1-3.12c.22-.41.42-.76.44-.79s.5.43.9,1.24L12,5ZM13.41,3a2.84,2.84,0,0,1-.45.64,11,11,0,0,1-.9-.91l-.84-.9.19-.45c.34-.79.39-.8,1-.31A9.4,9.4,0,0,1,13.8,2.33q-.18.34-.39.69Z"
|
|
/>
|
|
</svg>
|
|
<div class="app-name">
|
|
<div class="app-name__company">RanchiMall</div>
|
|
<h4 class="app-name__title">Binance Smart Chain</h4>
|
|
</div>
|
|
</div>
|
|
<button
|
|
id="meta_mask_status_button"
|
|
class="button interactive flex align-center hidden"
|
|
data-status="disconnected"
|
|
onclick="connectToMetaMask()"
|
|
>
|
|
<div class="icon-wrapper">
|
|
<svg
|
|
class="icon"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
xml:space="preserve"
|
|
id="Layer_1"
|
|
x="0"
|
|
y="0"
|
|
version="1.1"
|
|
viewBox="0 0 318.6 318.6"
|
|
>
|
|
<style>
|
|
.st1,
|
|
.st6 {
|
|
fill: #e4761b;
|
|
stroke: #e4761b;
|
|
stroke-linecap: round;
|
|
stroke-linejoin: round;
|
|
}
|
|
|
|
.st6 {
|
|
fill: #f6851b;
|
|
stroke: #f6851b;
|
|
}
|
|
</style>
|
|
<path
|
|
fill="#e2761b"
|
|
stroke="#e2761b"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="m274.1 35.5-99.5 73.9L193 65.8z"
|
|
/>
|
|
<path
|
|
d="m44.4 35.5 98.7 74.6-17.5-44.3zm193.9 171.3-26.5 40.6 56.7 15.6 16.3-55.3zm-204.4.9L50.1 263l56.7-15.6-26.5-40.6z"
|
|
class="st1"
|
|
/>
|
|
<path
|
|
d="m103.6 138.2-15.8 23.9 56.3 2.5-2-60.5zm111.3 0-39-34.8-1.3 61.2 56.2-2.5zM106.8 247.4l33.8-16.5-29.2-22.8zm71.1-16.5 33.9 16.5-4.7-39.3z"
|
|
class="st1"
|
|
/>
|
|
<path
|
|
fill="#d7c1b3"
|
|
stroke="#d7c1b3"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="m211.8 247.4-33.9-16.5 2.7 22.1-.3 9.3zm-105 0 31.5 14.9-.2-9.3 2.5-22.1z"
|
|
/>
|
|
<path
|
|
fill="#233447"
|
|
stroke="#233447"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="m138.8 193.5-28.2-8.3 19.9-9.1zm40.9 0 8.3-17.4 20 9.1z"
|
|
/>
|
|
<path
|
|
fill="#cd6116"
|
|
stroke="#cd6116"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="m106.8 247.4 4.8-40.6-31.3.9zM207 206.8l4.8 40.6 26.5-39.7zm23.8-44.7-56.2 2.5 5.2 28.9 8.3-17.4 20 9.1zm-120.2 23.1 20-9.1 8.2 17.4 5.3-28.9-56.3-2.5z"
|
|
/>
|
|
<path
|
|
fill="#e4751f"
|
|
stroke="#e4751f"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="m87.8 162.1 23.6 46-.8-22.9zm120.3 23.1-1 22.9 23.7-46zm-64-20.6-5.3 28.9 6.6 34.1 1.5-44.9zm30.5 0-2.7 18 1.2 45 6.7-34.1z"
|
|
/>
|
|
<path
|
|
d="m179.8 193.5-6.7 34.1 4.8 3.3 29.2-22.8 1-22.9zm-69.2-8.3.8 22.9 29.2 22.8 4.8-3.3-6.6-34.1z"
|
|
class="st6"
|
|
/>
|
|
<path
|
|
fill="#c0ad9e"
|
|
stroke="#c0ad9e"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="m180.3 262.3.3-9.3-2.5-2.2h-37.7l-2.3 2.2.2 9.3-31.5-14.9 11 9 22.3 15.5h38.3l22.4-15.5 11-9z"
|
|
/>
|
|
<path
|
|
fill="#161616"
|
|
stroke="#161616"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="m177.9 230.9-4.8-3.3h-27.7l-4.8 3.3-2.5 22.1 2.3-2.2h37.7l2.5 2.2z"
|
|
/>
|
|
<path
|
|
fill="#763d16"
|
|
stroke="#763d16"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="m278.3 114.2 8.5-40.8-12.7-37.9-96.2 71.4 37 31.3 52.3 15.3 11.6-13.5-5-3.6 8-7.3-6.2-4.8 8-6.1zM31.8 73.4l8.5 40.8-5.4 4 8 6.1-6.1 4.8 8 7.3-5 3.6 11.5 13.5 52.3-15.3 37-31.3-96.2-71.4z"
|
|
/>
|
|
<path
|
|
d="m267.2 153.5-52.3-15.3 15.9 23.9-23.7 46 31.2-.4h46.5zm-163.6-15.3-52.3 15.3-17.4 54.2h46.4l31.1.4-23.6-46zm71 26.4 3.3-57.7 15.2-41.1h-67.5l15 41.1 3.5 57.7 1.2 18.2.1 44.8h27.7l.2-44.8z"
|
|
class="st6"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div id="meta_mask_status">Disconnected</div>
|
|
</button>
|
|
<div class="flex align-center gap-0-3">
|
|
<sm-select id="currency_selector" class="margin-right-0-5">
|
|
<sm-option value="bsc" selected>BSC</sm-option>
|
|
<sm-option value="inr">INR</sm-option>
|
|
<sm-option value="usd">USD</sm-option>
|
|
</sm-select>
|
|
<theme-toggle></theme-toggle>
|
|
</div>
|
|
</header>
|
|
<nav id="main_navbar">
|
|
<ul>
|
|
<li>
|
|
<a class="nav-item nav-item--active interactive" href="#/balance">
|
|
<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="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>
|
|
<span class="nav-item__title"> Balance </span>
|
|
<div class="nav-item__indicator"></div>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="nav-item interactive" href="#/send">
|
|
<svg
|
|
class="icon"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
height="24px"
|
|
viewBox="0 0 24 24"
|
|
width="24px"
|
|
fill="#000000"
|
|
>
|
|
<path d="M0 0h24v24H0V0z" fill="none"></path>
|
|
<path
|
|
d="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"
|
|
></path>
|
|
</svg>
|
|
<span class="nav-item__title"> Send </span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="nav-item interactive" href="#/create">
|
|
<svg
|
|
class="icon"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
height="24px"
|
|
viewBox="0 0 24 24"
|
|
width="24px"
|
|
fill="#000000"
|
|
>
|
|
<path d="M0 0h24v24H0V0z" fill="none"></path>
|
|
<path
|
|
d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.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>
|
|
<span class="nav-item__title"> Create </span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
<div id="page_container"></div>
|
|
</main>
|
|
<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>
|
|
<div id="transaction_result_popup__content" class="grid gap-2"></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 addresses?</h4>
|
|
<p>
|
|
If you have your BSC/BTC/FLO Private Key, enter it here to recover
|
|
all your addresses and private keys.
|
|
</p>
|
|
</div>
|
|
<sm-form>
|
|
<sm-input
|
|
id="retrieve_btc_addr_field"
|
|
type="password"
|
|
placeholder="BSC/BTC/FLO 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="retrieveBinanceAddr()"
|
|
>
|
|
Recover
|
|
</button>
|
|
</sm-form>
|
|
<div id="recovered_addresses_wrapper" class="grid gap-1"></div>
|
|
</section>
|
|
</sm-popup>
|
|
<script>
|
|
/* Constants for FLO blockchain operations !!Make sure to add this at beginning!! */
|
|
const floGlobals = {
|
|
blockchain: "FLO",
|
|
tokenURL: "https://ranchimallflo.ranchimall.net/",
|
|
expirationDays: 60,
|
|
};
|
|
</script>
|
|
<script src="https://unpkg.com/uhtml@3.0.1/es.js"></script>
|
|
<script src="scripts/components.min.js" type="text/javascript"></script>
|
|
<script
|
|
src="scripts/btcwallet_scripts_lib.js"
|
|
type="text/javascript"
|
|
></script>
|
|
|
|
<!-- ethers.js version 5.6 -->
|
|
|
|
<script src="scripts/btcOperator.js" type="text/javascript"></script>
|
|
<script src="scripts/floCrypto.js" type="text/javascript"></script>
|
|
<script src="scripts/tap_combined.js" type="text/javascript"></script>
|
|
<script src="scripts/keccak.js" type="text/javascript"></script>
|
|
<script src="scripts/floEthereum.js" type="text/javascript"></script>
|
|
<script src="scripts/compactIDB.js" type="text/javascript"></script>
|
|
<script src="scripts/ether.umd.min.js" type="text/javascript"></script>
|
|
<script src="scripts/bscOperator.js" type="text/javascript"></script>
|
|
|
|
<script>
|
|
let showCurrentValue = false;
|
|
let currentCurrency = localStorage.getItem("preferredCurrency") || "bsc";
|
|
let exchangeRates = {
|
|
usd: 0,
|
|
inr: 0,
|
|
};
|
|
let historicalRates = {};
|
|
let isHistoricApiAvailable = false;
|
|
|
|
function syncCurrencySelector() {
|
|
const selector = getRef("currency_selector");
|
|
if (selector && selector.value !== currentCurrency) {
|
|
selector.value = currentCurrency;
|
|
const options = selector.querySelectorAll("sm-option");
|
|
options.forEach((option) => {
|
|
if (option.value === currentCurrency) {
|
|
option.setAttribute("selected", "");
|
|
} else {
|
|
option.removeAttribute("selected");
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
async function fetchExchangeRates() {
|
|
try {
|
|
const response = await fetch(
|
|
"https://api.coingecko.com/api/v3/simple/price?ids=binancecoin&vs_currencies=usd,inr"
|
|
);
|
|
const data = await response.json();
|
|
|
|
if (data) {
|
|
exchangeRates = {
|
|
usd: parseFloat(data.binancecoin.usd) || 0,
|
|
inr: parseFloat(data.binancecoin.inr) || 0,
|
|
};
|
|
} else {
|
|
exchangeRates = {
|
|
usd: 500, // Fallback rate
|
|
inr: 40000, // Fallback rate
|
|
};
|
|
}
|
|
return exchangeRates;
|
|
} catch (error) {
|
|
console.error("Error fetching exchange rates:", error);
|
|
notify("Failed to fetch currency rates", "error");
|
|
exchangeRates = {
|
|
usd: 500, // Fallback rate
|
|
inr: 40000, // Fallback rate
|
|
};
|
|
isHistoricApiAvailable = false;
|
|
return exchangeRates;
|
|
}
|
|
}
|
|
|
|
async function fetchHistoricalRate(timestamp) {
|
|
if (historicalRates[timestamp]) {
|
|
return historicalRates[timestamp];
|
|
}
|
|
const cacheKey = `bsc-historical-${timestamp}`;
|
|
const cachedData = localStorage.getItem(cacheKey);
|
|
if (cachedData) {
|
|
const parsedData = JSON.parse(cachedData);
|
|
historicalRates[timestamp] = parsedData;
|
|
return parsedData;
|
|
}
|
|
|
|
try {
|
|
const date = new Date(timestamp * 1000);
|
|
const day = date.getDate().toString().padStart(2, "0");
|
|
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
const year = date.getFullYear();
|
|
const formattedDate = `${day}-${month}-${year}`;
|
|
const response = await fetch(
|
|
`https://api.coingecko.com/api/v3/coins/binancecoin/history?date=${formattedDate}`
|
|
);
|
|
const data = await response.json();
|
|
|
|
if (data && data.market_data && data.market_data.current_price) {
|
|
const rates = {
|
|
usd: parseFloat(data.market_data.current_price.usd) || 0,
|
|
inr: parseFloat(data.market_data.current_price.inr) || 0,
|
|
};
|
|
historicalRates[timestamp] = rates;
|
|
localStorage.setItem(cacheKey, JSON.stringify(rates));
|
|
return rates;
|
|
}
|
|
return exchangeRates;
|
|
} catch (error) {
|
|
console.error("Error fetching historical rate:", error);
|
|
return exchangeRates;
|
|
}
|
|
}
|
|
|
|
async function updateCurrency(currency) {
|
|
const previousCurrency = currentCurrency;
|
|
currentCurrency = currency;
|
|
localStorage.setItem("preferredCurrency", currency);
|
|
|
|
if (
|
|
currency !== "bsc" &&
|
|
(exchangeRates.usd === 0 || exchangeRates.inr === 0)
|
|
) {
|
|
await fetchExchangeRates();
|
|
}
|
|
if (getRef("page_container")?.dataset?.page === "tdx") {
|
|
// Get the transaction value and fee elements if they exist
|
|
const valueElement = getRef("tx_value");
|
|
const feeElement = getRef("tx_fee");
|
|
|
|
if (valueElement && valueElement.dataset.eth) {
|
|
const ethAmount = parseFloat(valueElement.dataset.eth);
|
|
const timestamp = valueElement.dataset.timestamp;
|
|
valueElement.textContent = await getConvertedAmount(
|
|
ethAmount,
|
|
timestamp
|
|
);
|
|
}
|
|
|
|
if (feeElement && feeElement.dataset.eth) {
|
|
const ethFee = parseFloat(feeElement.dataset.eth);
|
|
const timestamp = feeElement.dataset.timestamp;
|
|
feeElement.textContent = await getConvertedAmount(
|
|
ethFee,
|
|
timestamp
|
|
);
|
|
}
|
|
}
|
|
|
|
if (currency !== "bsc" && previousCurrency === "bsc") {
|
|
testHistoricalAPIAndRenderToggle();
|
|
} else if (currency === "bsc" && previousCurrency !== "bsc") {
|
|
const container = document.querySelector(
|
|
".flex.flex-direction-column.gap-0-5.sticky.top-0"
|
|
);
|
|
if (container) {
|
|
const existingToggle =
|
|
container.querySelector("sm-switch")?.parentElement;
|
|
if (existingToggle) existingToggle.remove();
|
|
}
|
|
}
|
|
|
|
updateTransactionDetailsToggle();
|
|
convertAllDisplayedCurrency();
|
|
syncCurrencySelector();
|
|
}
|
|
|
|
function handleValuationTypeChange(isCurrentValue) {
|
|
if (isCurrentValue && isCurrentValue.target) {
|
|
isCurrentValue = isCurrentValue.target.checked;
|
|
}
|
|
showCurrentValue = isCurrentValue;
|
|
localStorage.setItem("bsc-wallet-show-current-value", isCurrentValue);
|
|
convertAllDisplayedCurrency();
|
|
}
|
|
|
|
async function convertAllDisplayedCurrency() {
|
|
const currency = currentCurrency;
|
|
const elements = document.querySelectorAll(".bsc-value");
|
|
|
|
for (const el of elements) {
|
|
const bscAmount = parseFloat(el.dataset.bsc || "0");
|
|
const timestamp = el.dataset.timestamp;
|
|
if (el.id === "usdc_balance" || el.id === "usdt_balance") {
|
|
continue;
|
|
}
|
|
let formattedValue;
|
|
if (currency === "bsc") {
|
|
formattedValue = formatCurrency(bscAmount);
|
|
} else {
|
|
formattedValue = await getConvertedAmount(bscAmount, timestamp);
|
|
}
|
|
|
|
el.textContent = formattedValue;
|
|
}
|
|
}
|
|
|
|
async function getConvertedAmount(amountInBsc, timestamp) {
|
|
if (!amountInBsc) return "0 BSC";
|
|
|
|
const value = Number(amountInBsc);
|
|
if (isNaN(value)) return amountInBsc;
|
|
|
|
if (currentCurrency === "bsc") {
|
|
return `${value.toFixed(6)} BSC`;
|
|
} else {
|
|
let rates = exchangeRates;
|
|
|
|
if (!showCurrentValue && timestamp) {
|
|
try {
|
|
const historicalRates = await fetchHistoricalRate(timestamp);
|
|
if (historicalRates) {
|
|
rates = historicalRates;
|
|
}
|
|
} catch (error) {
|
|
console.error(
|
|
"Error getting historical rates, using current rates:",
|
|
error
|
|
);
|
|
}
|
|
}
|
|
if (rates && currentCurrency === "usd" && rates.usd) {
|
|
return `${(value * rates.usd).toFixed(2)} USD`;
|
|
} else if (rates && currentCurrency === "inr" && rates.inr) {
|
|
return `${(value * rates.inr).toFixed(2)} INR`;
|
|
}
|
|
return `${value.toFixed(6)} BSC`;
|
|
}
|
|
}
|
|
|
|
function formatCurrency(amountInBsc) {
|
|
if (!amountInBsc) return "0 BSC";
|
|
|
|
const value = Number(amountInBsc);
|
|
if (isNaN(value)) return amountInBsc;
|
|
|
|
if (currentCurrency === "bsc") {
|
|
return `${value.toFixed(6)} BSC`;
|
|
} else if (currentCurrency === "usd") {
|
|
return `${(value * exchangeRates.usd).toFixed(2)} USD`;
|
|
} else if (currentCurrency === "inr") {
|
|
return `${(value * exchangeRates.inr).toFixed(2)} INR`;
|
|
}
|
|
|
|
return `${value.toFixed(6)} BSC`;
|
|
}
|
|
|
|
function initCurrencySelector() {
|
|
const selector = getRef("currency_selector");
|
|
if (selector) {
|
|
selector.addEventListener("change", (e) => {
|
|
updateCurrency(e.target.value);
|
|
});
|
|
}
|
|
|
|
const stored = localStorage.getItem("preferredCurrency");
|
|
if (stored) {
|
|
currentCurrency = stored;
|
|
showCurrentValue =
|
|
localStorage.getItem("bsc-wallet-show-current-value") === "true";
|
|
syncCurrencySelector();
|
|
|
|
if (
|
|
currentCurrency !== "bsc" &&
|
|
(exchangeRates.usd === 0 || exchangeRates.inr === 0)
|
|
) {
|
|
fetchExchangeRates().then(() => {
|
|
convertAllDisplayedCurrency();
|
|
testHistoricalAPIAndRenderToggle();
|
|
});
|
|
} else if (currentCurrency !== "bsc") {
|
|
testHistoricalAPIAndRenderToggle();
|
|
}
|
|
}
|
|
}
|
|
|
|
const uiGlobals = {};
|
|
const { html, svg, render: renderElem } = uhtml;
|
|
uiGlobals.connectionErrorNotification = [];
|
|
//Checks for internet connection status
|
|
if (!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((notification) => {
|
|
getRef("notification_drawer").remove(notification);
|
|
});
|
|
notify("We are back online.", "success");
|
|
});
|
|
// Use instead of document.getElementById
|
|
function getRef(elementId) {
|
|
return document.getElementById(elementId);
|
|
}
|
|
let zIndex = 50;
|
|
// function required for popups or modals to appear
|
|
function openPopup(popupId, pinned) {
|
|
if (popupStack.peek() === undefined) {
|
|
document.addEventListener("keydown", (e) => {
|
|
if (e.key === "Escape") {
|
|
closePopup();
|
|
}
|
|
});
|
|
}
|
|
zIndex++;
|
|
getRef(popupId).setAttribute("style", `z-index: ${zIndex}`);
|
|
return getRef(popupId).show({ pinned });
|
|
}
|
|
|
|
// hides the popup or modal
|
|
function closePopup(options = {}) {
|
|
if (popupStack.peek() === undefined) return;
|
|
popupStack.peek().popup.hide(options);
|
|
}
|
|
|
|
document.addEventListener("popupopened", async (e) => {
|
|
switch (e.target.id) {
|
|
}
|
|
});
|
|
document.addEventListener("popupclosed", (e) => {
|
|
zIndex--;
|
|
switch (e.target.id) {
|
|
case "retrieve_btc_addr_popup":
|
|
getRef("recovered_btc_addr_wrapper").classList.add("hidden");
|
|
break;
|
|
}
|
|
});
|
|
//Function for displaying toast notifications. pass in error for mode param if you want to show an error.
|
|
function notify(message, mode, options = {}) {
|
|
let icon;
|
|
switch (mode) {
|
|
case "success":
|
|
icon = `<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":
|
|
icon = `<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>`;
|
|
options.pinned = true;
|
|
break;
|
|
}
|
|
if (mode === "error") {
|
|
console.error(message);
|
|
}
|
|
return getRef("notification_drawer").push(message, {
|
|
icon,
|
|
...options,
|
|
});
|
|
}
|
|
// displays a popup for asking permission. Use this instead of JS confirm
|
|
/**
|
|
@param {string} title - Title of the popup
|
|
@param {object} options - Options for the popup
|
|
@param {string} options.message - Message to be displayed in the popup
|
|
@param {string} options.cancelText - Text for the cancel button
|
|
@param {string} options.confirmText - Text for the confirm button
|
|
@param {boolean} options.danger - If true, confirm button will be red
|
|
*/
|
|
const getConfirmation = (title, options = {}) => {
|
|
return new Promise((resolve) => {
|
|
const {
|
|
message = "",
|
|
cancelText = "Cancel",
|
|
confirmText = "OK",
|
|
danger = false,
|
|
} = options;
|
|
getRef("confirm_title").innerText = title;
|
|
getRef("confirm_message").innerText = message;
|
|
const cancelButton =
|
|
getRef("confirmation_popup").querySelector(".cancel-button");
|
|
const confirmButton =
|
|
getRef("confirmation_popup").querySelector(".confirm-button");
|
|
confirmButton.textContent = confirmText;
|
|
cancelButton.textContent = cancelText;
|
|
if (danger) confirmButton.classList.add("button--danger");
|
|
else confirmButton.classList.remove("button--danger");
|
|
const { opened, closed } = openPopup("confirmation_popup");
|
|
confirmButton.onclick = () => {
|
|
closePopup({ payload: true });
|
|
};
|
|
cancelButton.onclick = () => {
|
|
closePopup();
|
|
};
|
|
closed.then((payload) => {
|
|
confirmButton.onclick = null;
|
|
cancelButton.onclick = null;
|
|
if (payload) resolve(true);
|
|
else resolve(false);
|
|
});
|
|
});
|
|
};
|
|
function createRipple(event, target) {
|
|
const circle = document.createElement("span");
|
|
const diameter = Math.max(target.clientWidth, target.clientHeight);
|
|
const radius = diameter / 2;
|
|
const targetDimensions = target.getBoundingClientRect();
|
|
circle.style.width = circle.style.height = `${diameter}px`;
|
|
circle.style.left = `${
|
|
event.clientX - (targetDimensions.left + radius)
|
|
}px`;
|
|
circle.style.top = `${
|
|
event.clientY - (targetDimensions.top + radius)
|
|
}px`;
|
|
circle.classList.add("ripple");
|
|
const rippleAnimation = circle.animate(
|
|
[
|
|
{
|
|
opacity: 1,
|
|
transform: `scale(0)`,
|
|
},
|
|
{
|
|
transform: "scale(4)",
|
|
opacity: 0,
|
|
},
|
|
],
|
|
{
|
|
duration: 600,
|
|
fill: "forwards",
|
|
easing: "ease-out",
|
|
}
|
|
);
|
|
target.append(circle);
|
|
rippleAnimation.onfinish = () => {
|
|
circle.remove();
|
|
};
|
|
}
|
|
function buttonLoader(id, show) {
|
|
const button =
|
|
typeof id === "string" ? document.getElementById(id) : id;
|
|
if (!button) return;
|
|
if (!button.dataset.hasOwnProperty("wasDisabled"))
|
|
button.dataset.wasDisabled = button.disabled;
|
|
const animOptions = {
|
|
duration: 200,
|
|
fill: "forwards",
|
|
easing: "ease",
|
|
};
|
|
if (show) {
|
|
button.disabled = true;
|
|
button.parentNode.append(document.createElement("sm-spinner"));
|
|
button.animate(
|
|
[
|
|
{
|
|
clipPath: "circle(100%)",
|
|
},
|
|
{
|
|
clipPath: "circle(0)",
|
|
},
|
|
],
|
|
animOptions
|
|
);
|
|
} else {
|
|
button.disabled = button.dataset.wasDisabled === "true";
|
|
button.animate(
|
|
[
|
|
{
|
|
clipPath: "circle(0)",
|
|
},
|
|
{
|
|
clipPath: "circle(100%)",
|
|
},
|
|
],
|
|
animOptions
|
|
).onfinish = (e) => {
|
|
button.removeAttribute("data-original-state");
|
|
};
|
|
const potentialTarget = button.parentNode.querySelector("sm-spinner");
|
|
if (potentialTarget) potentialTarget.remove();
|
|
}
|
|
}
|
|
class Router {
|
|
/**
|
|
* @constructor {object} options - options for the router
|
|
* @param {object} options.routes - routes for the router
|
|
* @param {object} options.state - initial state for the router
|
|
* @param {function} options.routingStart - function to be called before routing
|
|
* @param {function} options.routingEnd - function to be called after routing
|
|
*/
|
|
constructor(options = {}) {
|
|
const { routes = {}, state = {}, routingStart, routingEnd } = options;
|
|
this.routes = routes;
|
|
this.state = state;
|
|
this.routingStart = routingStart;
|
|
this.routingEnd = routingEnd;
|
|
this.lastPage = null;
|
|
window.addEventListener("hashchange", (e) =>
|
|
this.routeTo(window.location.hash)
|
|
);
|
|
}
|
|
/**
|
|
* @param {string} route - route to be added
|
|
* @param {function} callback - function to be called when route is matched
|
|
*/
|
|
addRoute(route, callback) {
|
|
this.routes[route] = callback;
|
|
}
|
|
/**
|
|
* @param {string} route
|
|
*/
|
|
handleRouting = async (page) => {
|
|
if (this.routingStart) {
|
|
this.routingStart(this.state);
|
|
}
|
|
if (this.routes[page]) {
|
|
await this.routes[page](this.state);
|
|
this.lastPage = page;
|
|
} else {
|
|
if (this.routes["404"]) {
|
|
this.routes["404"](this.state);
|
|
} else {
|
|
console.error(
|
|
`No route found for '${page}' and no '404' route is defined.`
|
|
);
|
|
}
|
|
}
|
|
if (this.routingEnd) {
|
|
this.routingEnd(this.state);
|
|
}
|
|
};
|
|
async routeTo(destination) {
|
|
try {
|
|
let page;
|
|
let wildcards = [];
|
|
let params = {};
|
|
let [path, queryString] = destination.split("?");
|
|
if (path.includes("#")) path = path.split("#")[1];
|
|
if (path.includes("/")) [, page, ...wildcards] = path.split("/");
|
|
else page = path;
|
|
this.state = { page, wildcards, lastPage: this.lastPage, params };
|
|
if (queryString) {
|
|
params = new URLSearchParams(queryString);
|
|
this.state.params = Object.fromEntries(params);
|
|
}
|
|
if (document.startViewTransition) {
|
|
document.startViewTransition(async () => {
|
|
await this.handleRouting(page);
|
|
});
|
|
} else {
|
|
// Fallback for browsers that don't support View transition API:
|
|
await this.handleRouting(page);
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
<script>
|
|
const assetIcons = {
|
|
Binance: `<svg hight="34" width="34" version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
viewBox="0 0 800 600" style="enable-background:new 0 0 800 600;" xml:space="preserve">
|
|
<style type="text/css">
|
|
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#F3BA2F;}
|
|
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#131415;}
|
|
</style>
|
|
<g id="Light">
|
|
<g id="OneArt-_x2022_-Desktop-_x2022_-Light" transform="translate(-457.000000, -1515.000000)">
|
|
<g id="Block" transform="translate(41.000000, 1263.000000)">
|
|
<g id="TVL" transform="translate(48.000000, 252.000000)">
|
|
<g id="Icons_x2F_Icon-24_x2F_cake" transform="translate(368.000000, 0.000000)">
|
|
<circle id="Oval" class="st0" cx="399.8" cy="299.6" r="230.7"/>
|
|
<g id="Icons_x2F_icon-24_x2F_networks_x2F_binance_x5F_smart_x5F_chain" transform="translate(3.333333, 3.333333)">
|
|
<path id="Combined-Shape" class="st1" d="M456.3,320.8l34.8,34.7L396.5,450L302,355.5l34.8-34.7l59.7,59.7L456.3,320.8z
|
|
M396.5,261l35.3,35.3h0l0,0l-35.3,35.3l-35.2-35.2l0-0.1l0,0l6.2-6.2l3-3L396.5,261z M277.5,261.5l34.8,34.8L277.5,331
|
|
l-34.8-34.8L277.5,261.5z M515.5,261.5l34.8,34.8L515.5,331l-34.8-34.8L515.5,261.5z M396.5,142.5L491,237l-34.8,34.8
|
|
L396.5,212l-59.7,59.7L302,237L396.5,142.5z"/>
|
|
</g>
|
|
</g>
|
|
</g>
|
|
</g>
|
|
</g>
|
|
</g>
|
|
</svg>`,
|
|
usdc: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" data-name="86977684-12db-4850-8f30-233a7c267d11" viewBox="0 0 2000 2000"> <path d="M1000 2000c554.17 0 1000-445.83 1000-1000S1554.17 0 1000 0 0 445.83 0 1000s445.83 1000 1000 1000z" fill="#2775ca"/> <path d="M1275 1158.33c0-145.83-87.5-195.83-262.5-216.66-125-16.67-150-50-150-108.34s41.67-95.83 125-95.83c75 0 116.67 25 137.5 87.5 4.17 12.5 16.67 20.83 29.17 20.83h66.66c16.67 0 29.17-12.5 29.17-29.16v-4.17c-16.67-91.67-91.67-162.5-187.5-170.83v-100c0-16.67-12.5-29.17-33.33-33.34h-62.5c-16.67 0-29.17 12.5-33.34 33.34v95.83c-125 16.67-204.16 100-204.16 204.17 0 137.5 83.33 191.66 258.33 212.5 116.67 20.83 154.17 45.83 154.17 112.5s-58.34 112.5-137.5 112.5c-108.34 0-145.84-45.84-158.34-108.34-4.16-16.66-16.66-25-29.16-25h-70.84c-16.66 0-29.16 12.5-29.16 29.17v4.17c16.66 104.16 83.33 179.16 220.83 200v100c0 16.66 12.5 29.16 33.33 33.33h62.5c16.67 0 29.17-12.5 33.34-33.33v-100c125-20.84 208.33-108.34 208.33-220.84z" fill="#fff"/> <path d="M787.5 1595.83c-325-116.66-491.67-479.16-370.83-800 62.5-175 200-308.33 370.83-370.83 16.67-8.33 25-20.83 25-41.67V325c0-16.67-8.33-29.17-25-33.33-4.17 0-12.5 0-16.67 4.16-395.83 125-612.5 545.84-487.5 941.67 75 233.33 254.17 412.5 487.5 487.5 16.67 8.33 33.34 0 37.5-16.67 4.17-4.16 4.17-8.33 4.17-16.66v-58.34c0-12.5-12.5-29.16-25-37.5zM1229.17 295.83c-16.67-8.33-33.34 0-37.5 16.67-4.17 4.17-4.17 8.33-4.17 16.67v58.33c0 16.67 12.5 33.33 25 41.67 325 116.66 491.67 479.16 370.83 800-62.5 175-200 308.33-370.83 370.83-16.67 8.33-25 20.83-25 41.67V1700c0 16.67 8.33 29.17 25 33.33 4.17 0 12.5 0 16.67-4.16 395.83-125 612.5-545.84 487.5-941.67-75-237.5-258.34-416.67-487.5-491.67z" fill="#fff"/></svg>`,
|
|
usdt: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 339.43 295.27"><title>tether-usdt-logo</title><path d="M62.15,1.45l-61.89,130a2.52,2.52,0,0,0,.54,2.94L167.95,294.56a2.55,2.55,0,0,0,3.53,0L338.63,134.4a2.52,2.52,0,0,0,.54-2.94l-61.89-130A2.5,2.5,0,0,0,275,0H64.45a2.5,2.5,0,0,0-2.3,1.45h0Z" style="fill:#50af95;fill-rule:evenodd"/><path d="M191.19,144.8v0c-1.2.09-7.4,0.46-21.23,0.46-11,0-18.81-.33-21.55-0.46v0c-42.51-1.87-74.24-9.27-74.24-18.13s31.73-16.25,74.24-18.15v28.91c2.78,0.2,10.74.67,21.74,0.67,13.2,0,19.81-.55,21-0.66v-28.9c42.42,1.89,74.08,9.29,74.08,18.13s-31.65,16.24-74.08,18.12h0Zm0-39.25V79.68h59.2V40.23H89.21V79.68H148.4v25.86c-48.11,2.21-84.29,11.74-84.29,23.16s36.18,20.94,84.29,23.16v82.9h42.78V151.83c48-2.21,84.12-11.73,84.12-23.14s-36.09-20.93-84.12-23.15h0Zm0,0h0Z" style="fill:#fff;fill-rule:evenodd"/><script xmlns=""/></svg>`,
|
|
};
|
|
window.smCompConfig = {
|
|
"sm-input": [
|
|
{
|
|
selector: "[data-BSC-address]",
|
|
customValidation: (value) => {
|
|
if (!value)
|
|
return {
|
|
isValid: false,
|
|
errorText: "Please enter a Binance address",
|
|
};
|
|
return {
|
|
isValid: bscOperator.isValidAddress(value),
|
|
errorText: `Invalid address.<br> It usually starts with "0x"`,
|
|
};
|
|
},
|
|
},
|
|
{
|
|
selector: "[data-private-key]",
|
|
customValidation: (value) => {
|
|
if (!value)
|
|
return {
|
|
isValid: false,
|
|
errorText: "Please enter a private key",
|
|
};
|
|
return {
|
|
isValid: floCrypto.getPubKeyHex(value),
|
|
errorText: `Invalid private key.`,
|
|
};
|
|
},
|
|
},
|
|
{
|
|
selector: "#check_balance_input",
|
|
customValidation: (value) => {
|
|
if (!value)
|
|
return {
|
|
isValid: false,
|
|
errorText: "Please enter a private key or BSC address",
|
|
};
|
|
|
|
if (/^0x[0-9a-fA-F]{64}$/.test(value)) {
|
|
return { isValid: true };
|
|
}
|
|
|
|
return {
|
|
isValid:
|
|
floCrypto.getPubKeyHex(value) ||
|
|
bscOperator.isValidAddress(value),
|
|
errorText: `Invalid private key or BSC address"`,
|
|
};
|
|
},
|
|
},
|
|
],
|
|
};
|
|
const router = new Router({
|
|
routingStart(state) {},
|
|
routingEnd(state) {
|
|
let { page } = state;
|
|
if (!page) page = "balance";
|
|
|
|
const previousTarget =
|
|
getRef("main_navbar").querySelector(".nav-item--active");
|
|
if (previousTarget) {
|
|
previousTarget.classList.remove("nav-item--active");
|
|
previousTarget.querySelector(".nav-item__indicator")?.remove();
|
|
}
|
|
|
|
if (page === "tdx") {
|
|
renderTdx(state);
|
|
} else {
|
|
const target = getRef("main_navbar").querySelector(
|
|
`.nav-item[href="#/${page}"]`
|
|
);
|
|
if (target) {
|
|
target.classList.add("nav-item--active");
|
|
target.append(html.node`<div class="nav-item__indicator"></div>`);
|
|
}
|
|
}
|
|
},
|
|
});
|
|
function setMetaMaskStatus(isConnected) {
|
|
if (isConnected) {
|
|
getRef("meta_mask_status").textContent = "Connected";
|
|
getRef("meta_mask_status_button").dataset.status = "connected";
|
|
} else {
|
|
getRef("meta_mask_status").textContent = "Disconnected";
|
|
getRef("meta_mask_status_button").dataset.status = "disconnected";
|
|
}
|
|
}
|
|
window.addEventListener("load", () => {
|
|
router.routeTo(location.hash);
|
|
document.body.classList.remove("hidden");
|
|
document.addEventListener("copy", () => {
|
|
notify("copied", "success");
|
|
});
|
|
currentCurrency = "bsc";
|
|
localStorage.setItem("preferredCurrency", "bsc");
|
|
syncCurrencySelector();
|
|
initCurrencySelector();
|
|
document.addEventListener("pointerdown", (e) => {
|
|
if (
|
|
e.target.closest(
|
|
"button:not(:disabled), .interactive:not(:disabled)"
|
|
)
|
|
) {
|
|
createRipple(e, e.target.closest("button, .interactive"));
|
|
}
|
|
});
|
|
|
|
compactIDB
|
|
.initDB("floBinance", {
|
|
contacts: {},
|
|
})
|
|
.then((result) => {})
|
|
.catch((error) => {
|
|
console.error(error);
|
|
});
|
|
// connectToMetaMask().then(() => {
|
|
if (window.ethereum) {
|
|
// setMetaMaskStatus(window.ethereum.isConnected())
|
|
window.ethereum.on("chainChanged", (chainId) => {
|
|
window.currentChainId = chainId;
|
|
if (chainId !== "0x38") {
|
|
renderError("Please switch MetaMask to Binance Mainnet");
|
|
} else {
|
|
router.routeTo(location.hash);
|
|
}
|
|
});
|
|
window.ethereum
|
|
.request({
|
|
method: "eth_chainId",
|
|
})
|
|
.then((chainId) => {
|
|
window.currentChainId = chainId;
|
|
if (chainId !== "0x38") {
|
|
renderError(
|
|
"Please switch MetaMask to Binance Smart Chain Mainnet"
|
|
);
|
|
} else {
|
|
router.routeTo(location.hash);
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error("Error fetching chain ID:", error);
|
|
renderError("Failed to fetch chain ID");
|
|
});
|
|
}
|
|
// }).catch((error) => {
|
|
// setMetaMaskStatus(false)
|
|
// if (error.code === 4001) {
|
|
// // EIP-1193 userRejectedRequest error
|
|
// notify('Please connect to MetaMask to continue', 'error')
|
|
// } else {
|
|
// if (error === 'MetaMask not installed') {
|
|
// getRef('balance_section').classList.add('hidden')
|
|
// getRef('error_section').classList.remove('hidden')
|
|
// }
|
|
// else
|
|
// console.error(error)
|
|
// }
|
|
// })
|
|
if (typeof window.ethereum !== "undefined") {
|
|
ethereum.on("accountsChanged", (accounts) => {
|
|
getRef("bsc_balance_wrapper").classList.add("hidden");
|
|
setMetaMaskStatus(accounts.length > 0);
|
|
});
|
|
ethereum.on("connect", (accounts) => {
|
|
setMetaMaskStatus(accounts.length > 0);
|
|
});
|
|
ethereum.on("disconnect", (accounts) => {
|
|
setMetaMaskStatus(false);
|
|
});
|
|
}
|
|
});
|
|
router.addRoute("404", () => {
|
|
renderElem(getRef("page_container"), html` <h1>Page not found</h1> `);
|
|
});
|
|
router.addRoute("", renderHome);
|
|
router.addRoute("balance", (state) => {
|
|
renderHome();
|
|
if (state.wildcards && state.wildcards[0]) {
|
|
checkBalance(state.wildcards[0]);
|
|
}
|
|
});
|
|
router.addRoute("tdx", renderTdx);
|
|
|
|
class LazyLoader {
|
|
constructor(container, elementsToRender, renderFn, options = {}) {
|
|
const { pageSize = 10, hasMore = false, onLoadMore = null } = options;
|
|
|
|
this.container = document.querySelector(container);
|
|
this.elementsToRender = elementsToRender;
|
|
this.renderFn = renderFn;
|
|
this.pageSize = pageSize;
|
|
this.currentPage = 1;
|
|
this.totalPages = Math.ceil(elementsToRender.length / pageSize);
|
|
this.hasMore = hasMore;
|
|
this.onLoadMore = onLoadMore;
|
|
}
|
|
|
|
init() {
|
|
this.container.innerHTML = "";
|
|
this.renderCurrentPage();
|
|
this.renderPagination();
|
|
}
|
|
|
|
renderCurrentPage() {
|
|
const start = (this.currentPage - 1) * this.pageSize;
|
|
const end = start + this.pageSize;
|
|
const currentElements = this.elementsToRender.slice(start, end);
|
|
|
|
currentElements.forEach((element) => {
|
|
this.container.appendChild(this.renderFn(element));
|
|
});
|
|
}
|
|
|
|
renderPagination() {
|
|
const existingPagination = document.querySelector(
|
|
".pagination-controls"
|
|
);
|
|
if (existingPagination) {
|
|
existingPagination.remove();
|
|
}
|
|
|
|
|
|
if (this.elementsToRender.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const paginationDiv = document.createElement("div");
|
|
paginationDiv.className = "pagination-controls";
|
|
paginationDiv.style.cssText = `width: 100%;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 0.75rem;
|
|
border-radius: 0.5rem;
|
|
background-color: rgba(var(--foreground-color), 1);
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
border: 1px solid rgba(var(--text-color), 0.1);
|
|
`;
|
|
|
|
// Previous button
|
|
const prevButton = document.createElement("button");
|
|
prevButton.className = "button";
|
|
prevButton.textContent = "Previous";
|
|
prevButton.disabled = this.currentPage === 1;
|
|
prevButton.onclick = () => this.goToPage(this.currentPage - 1);
|
|
|
|
// Next button
|
|
const nextButton = document.createElement("button");
|
|
nextButton.className = "button";
|
|
nextButton.textContent = "Next";
|
|
nextButton.disabled = this.currentPage === this.totalPages && !this.hasMore;
|
|
nextButton.onclick = () => {
|
|
if (this.currentPage < this.totalPages) {
|
|
this.goToPage(this.currentPage + 1);
|
|
} else if (this.hasMore && this.onLoadMore) {
|
|
this.onLoadMore();
|
|
}
|
|
};
|
|
|
|
// Page info
|
|
const pageInfo = document.createElement("span");
|
|
pageInfo.textContent = `Page ${this.currentPage}`;
|
|
|
|
paginationDiv.appendChild(prevButton);
|
|
paginationDiv.appendChild(pageInfo);
|
|
paginationDiv.appendChild(nextButton);
|
|
this.container.parentNode.insertBefore(
|
|
paginationDiv,
|
|
this.container.nextSibling
|
|
);
|
|
}
|
|
|
|
goToPage(pageNumber) {
|
|
if (pageNumber < 1 || pageNumber > this.totalPages) return;
|
|
this.currentPage = pageNumber;
|
|
this.container.innerHTML = "";
|
|
this.renderCurrentPage();
|
|
this.renderPagination();
|
|
}
|
|
|
|
update(newElements, hasMore = false, shouldAdvance = false) {
|
|
this.elementsToRender = newElements;
|
|
this.totalPages = Math.ceil(newElements.length / this.pageSize);
|
|
this.hasMore = hasMore;
|
|
if (shouldAdvance && this.currentPage < this.totalPages) {
|
|
this.currentPage++;
|
|
} else if (this.currentPage > this.totalPages) {
|
|
this.currentPage = this.totalPages;
|
|
}
|
|
this.container.innerHTML = "";
|
|
this.renderCurrentPage();
|
|
this.renderPagination();
|
|
}
|
|
|
|
clear() {
|
|
this.container.innerHTML = "";
|
|
this.elementsToRender = [];
|
|
this.currentPage = 1;
|
|
this.totalPages = 0;
|
|
const paginationDiv = document.querySelector(".pagination-controls");
|
|
if (paginationDiv) {
|
|
paginationDiv.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize transactions variable
|
|
let transactionsLazyLoader;
|
|
let allTransactions = [];
|
|
let currentCursor = null;
|
|
let currentAddress = null;
|
|
let isLoadingMore = false;
|
|
|
|
// Add render object with transaction rendering functions
|
|
const render = {
|
|
async transactions(address) {
|
|
try {
|
|
if (!address || !bscOperator.isValidAddress(address)) {
|
|
return;
|
|
}
|
|
// Reset if new address
|
|
if (address !== currentAddress) {
|
|
allTransactions = [];
|
|
currentCursor = null;
|
|
currentAddress = address;
|
|
}
|
|
|
|
getRef("address_transactions").classList.remove("hidden");
|
|
getRef("transactions_list").innerHTML =
|
|
'<sm-spinner class="justify-self-center margin-top-1-5"></sm-spinner>';
|
|
|
|
Promise.all([bscOperator.getTransactionHistory(address, currentCursor)]).then(
|
|
async ([response]) => {
|
|
if (!response || response.error) {
|
|
getRef("transactions_list").textContent =
|
|
"Error fetching transactions";
|
|
return;
|
|
}
|
|
// Extract transactions array from response object
|
|
const data = response.transactions || response;
|
|
currentCursor = response.nextCursor || null;
|
|
const hasMore = response.hasMore || false;
|
|
|
|
if (data && data.length > 0) {
|
|
const transactions = data.map((tx) => {
|
|
const isOutgoing =
|
|
tx.from.toUpperCase() === address.toUpperCase();
|
|
const txObj = {
|
|
txid: tx.hash,
|
|
time: getFormattedTime(tx.timeStamp),
|
|
originalTimestamp: tx.timeStamp,
|
|
block: tx.blockNumber || -1,
|
|
confirmations: tx.confirmations || 0,
|
|
type: isOutgoing ? "out" : "in",
|
|
amount: tx.value / 1000000000000000000,
|
|
sender: tx.from,
|
|
receiver: tx.to,
|
|
address: address,
|
|
};
|
|
return txObj;
|
|
});
|
|
|
|
// Add new transactions to the collection
|
|
allTransactions = allTransactions.concat(transactions);
|
|
|
|
// Filter
|
|
const filter = getRef("filter_selector").value || "all";
|
|
let filteredTransactions = allTransactions;
|
|
|
|
if (filter === "sent") {
|
|
filteredTransactions = allTransactions.filter(
|
|
(tx) => tx.type === "out"
|
|
);
|
|
} else if (filter === "received") {
|
|
filteredTransactions = allTransactions.filter(
|
|
(tx) => tx.type === "in"
|
|
);
|
|
}
|
|
|
|
if (transactionsLazyLoader) {
|
|
transactionsLazyLoader.update(filteredTransactions, hasMore, isLoadingMore);
|
|
isLoadingMore = false;
|
|
} else {
|
|
transactionsLazyLoader = new LazyLoader(
|
|
"#transactions_list",
|
|
filteredTransactions,
|
|
render.transactionCard,
|
|
{
|
|
pageSize: 10,
|
|
hasMore: hasMore,
|
|
onLoadMore: () => {
|
|
if (currentCursor) {
|
|
isLoadingMore = true;
|
|
render.transactions(currentAddress);
|
|
}
|
|
}
|
|
}
|
|
);
|
|
transactionsLazyLoader.init();
|
|
}
|
|
getRef("filter_selector").addEventListener("change", (e) => {
|
|
const newFilter = e.target.value;
|
|
let newFilteredTransactions = transactions;
|
|
if (newFilter === "sent") {
|
|
newFilteredTransactions = transactions.filter(
|
|
(tx) => tx.type === "out"
|
|
);
|
|
} else if (newFilter === "received") {
|
|
newFilteredTransactions = transactions.filter(
|
|
(tx) => tx.type === "in"
|
|
);
|
|
}
|
|
transactionsLazyLoader.update(newFilteredTransactions, currentCursor ? true : false);
|
|
});
|
|
const list = getRef("transactions_list");
|
|
list.onclick = (event) => {
|
|
const transactionCard =
|
|
event.target.closest(".transaction");
|
|
if (transactionCard && event.target === transactionCard) {
|
|
const txid = transactionCard.dataset.txid;
|
|
window.location.hash = `#/tdx/${txid}`;
|
|
}
|
|
};
|
|
getRef(
|
|
"transactions_list"
|
|
).previousElementSibling.classList.remove("hidden");
|
|
} else {
|
|
getRef("transactions_list").innerHTML = "";
|
|
if (transactionsLazyLoader) {
|
|
transactionsLazyLoader.clear();
|
|
transactionsLazyLoader = null;
|
|
}
|
|
getRef("transactions_list").textContent =
|
|
"No transactions found";
|
|
getRef("transactions_list").previousElementSibling.classList.add("hidden");
|
|
}
|
|
}
|
|
);
|
|
} catch (error) {
|
|
console.error("[DEBUG] Error fetching transactions:", error);
|
|
getRef("transactions_list").innerHTML = "";
|
|
if (transactionsLazyLoader) {
|
|
transactionsLazyLoader.clear();
|
|
transactionsLazyLoader = null;
|
|
}
|
|
getRef("transactions_list").textContent =
|
|
"Error fetching transactions";
|
|
}
|
|
},
|
|
|
|
transactionCard(transactionDetails) {
|
|
const transactionCard = document.createElement("li");
|
|
transactionCard.className = `transaction ${transactionDetails.type} ${
|
|
transactionDetails.block < 0 ? "unconfirmed-tx" : ""
|
|
}`;
|
|
transactionCard.dataset.txid = transactionDetails.txid;
|
|
transactionCard.style.cursor = "pointer";
|
|
transactionCard.onclick = () => {
|
|
window.location.hash = `#/tdx/${transactionDetails.txid}`;
|
|
};
|
|
|
|
let icon;
|
|
if (transactionDetails.type === "out") {
|
|
icon = `<svg class="icon sent" style="display: block; margin: auto;" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FF4B4B"><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>`;
|
|
} else {
|
|
icon = `<svg class="icon received" style="display: block; margin: auto;" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#4BC84B"><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>`;
|
|
}
|
|
|
|
const shortenAddress = (addr) => {
|
|
if (!addr) return "";
|
|
return (
|
|
addr.substring(0, 8) + "..." + addr.substring(addr.length - 6)
|
|
);
|
|
};
|
|
|
|
const fromAddress = transactionDetails.sender
|
|
? shortenAddress(transactionDetails.sender)
|
|
: "";
|
|
const toAddress = transactionDetails.receiver
|
|
? shortenAddress(transactionDetails.receiver)
|
|
: "";
|
|
const txHashShort = shortenAddress(transactionDetails.txid);
|
|
|
|
transactionCard.innerHTML = `
|
|
<div class="flex gap-1 align-center w-100 padding-0-5" style="border-radius: 0.5rem; border: 1px solid rgba(var(--text-color), 0.1); padding: 0.75rem; box-shadow: 0 2px 4px rgba(0,0,0,0.05);">
|
|
<div class="transaction__icon flex align-center justify-center" style="background-color: ${
|
|
transactionDetails.type === "out"
|
|
? "rgba(255,75,75,0.1)"
|
|
: "rgba(75,200,75,0.1)"
|
|
}; border-radius: 50%; min-width: 40px; height: 40px; padding: 8px;">
|
|
${icon}
|
|
</div>
|
|
<div class="grid gap-0-5 flex-1">
|
|
<div class="flex align-center space-between flex-wrap">
|
|
<div class="transaction__type" style="font-weight: 500; margin-right: 8px;">
|
|
${transactionDetails.type === "out" ? "Sent" : "Received"}
|
|
</div>
|
|
<time style="color: rgba(var(--text-color), 0.6); font-size: 0.85rem;">${
|
|
typeof transactionDetails.time === "string"
|
|
? transactionDetails.time
|
|
: getFormattedTime(transactionDetails.time)
|
|
}</time>
|
|
</div>
|
|
<div class="flex align-center space-between flex-wrap">
|
|
<div class="transaction__amount bsc-value" data-bsc="${
|
|
transactionDetails.amount
|
|
}" data-timestamp="${
|
|
transactionDetails.originalTimestamp || ""
|
|
}" style="font-weight: 600; color: ${
|
|
transactionDetails.type === "out"
|
|
? "var(--color-danger, #ff4b4b)"
|
|
: "var(--color-success, #4bc84b)"
|
|
}; margin-right: 8px; text-overflow: ellipsis; overflow: hidden; max-width: 100%;">
|
|
${formatCurrency(transactionDetails.amount)}
|
|
</div>
|
|
<div class="transaction__status" style="font-size: 0.85rem; color: rgba(var(--text-color), ${
|
|
transactionDetails.block < 0 ? "0.5" : "0.7"
|
|
});">
|
|
${
|
|
transactionDetails.block < 0
|
|
? "Pending"
|
|
: `${transactionDetails.confirmations} Confirmations`
|
|
}
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-wrap gap-0-5 margin-top-0-5">
|
|
${
|
|
transactionDetails.sender
|
|
? `<div class="flex align-center gap-0-3">
|
|
<span style="font-size: 0.85rem; color: rgba(var(--text-color), 0.7);">From:</span>
|
|
<span class="address-from interactive" data-address="${transactionDetails.sender}" style="font-size: 0.85rem; color: var(--color-primary); cursor: pointer;">${fromAddress}</span>
|
|
</div>`
|
|
: ""
|
|
}
|
|
${
|
|
transactionDetails.receiver
|
|
? `<div class="flex align-center gap-0-3">
|
|
<span style="font-size: 0.85rem; color: rgba(var(--text-color), 0.7);">To:</span>
|
|
<span class="address-to interactive" data-address="${transactionDetails.receiver}" style="font-size: 0.85rem; color: var(--color-primary); cursor: pointer;">${toAddress}</span>
|
|
</div>`
|
|
: ""
|
|
}
|
|
<div class="flex align-center gap-0-3">
|
|
<span style="font-size: 0.85rem; color: rgba(var(--text-color), 0.7);">Tx:</span>
|
|
<span class="txid interactive" data-txid="${
|
|
transactionDetails.txid
|
|
}" style="font-size: 0.85rem; color: var(--color-primary,#92a2ff); cursor: pointer;">${txHashShort}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Add event listeners
|
|
transactionCard
|
|
.querySelectorAll(".address-from, .address-to")
|
|
.forEach((el) => {
|
|
el.addEventListener("click", (e) => {
|
|
e.stopPropagation();
|
|
const address = el.dataset.address;
|
|
if (address) {
|
|
window.location.hash = `#/balance/${address}`;
|
|
}
|
|
});
|
|
});
|
|
|
|
transactionCard
|
|
.querySelector(".txid")
|
|
.addEventListener("click", (e) => {
|
|
e.stopPropagation();
|
|
});
|
|
|
|
transactionCard.classList.add("interactive");
|
|
return transactionCard;
|
|
},
|
|
|
|
queryResult(query) {
|
|
const type = checkQueryStringType(query);
|
|
|
|
if (type === "address") {
|
|
getRef("tx_details").classList.add("hidden");
|
|
getRef("address_transactions").classList.remove("hidden");
|
|
render.transactions(query);
|
|
|
|
if (bscOperator.isValidAddress(query)) {
|
|
checkBalance(query);
|
|
}
|
|
} else if (type === "txid") {
|
|
getRef("bsc_balance_wrapper").classList.add("hidden");
|
|
getRef("address_transactions").classList.add("hidden");
|
|
render.txDetails(query);
|
|
if (transactionsLazyLoader) {
|
|
transactionsLazyLoader.clear();
|
|
transactionsLazyLoader = null;
|
|
}
|
|
} else {
|
|
if (transactionsLazyLoader) {
|
|
transactionsLazyLoader.clear();
|
|
transactionsLazyLoader = null;
|
|
}
|
|
getRef("address_transactions").classList.add("hidden");
|
|
getRef("tx_details").classList.add("hidden");
|
|
notify("Invalid Binance address or transaction hash", "error");
|
|
}
|
|
},
|
|
|
|
// Transaction details view
|
|
async txDetails(txId) {
|
|
if (!txId || !/^0x([A-Fa-f0-9]{64})$/.test(txId)) {
|
|
notify("Invalid transaction hash", "error");
|
|
return;
|
|
}
|
|
|
|
getRef("tx_details").classList.remove("hidden");
|
|
renderElem(
|
|
getRef("tx_details"),
|
|
html`
|
|
<div class="tx-details-container">
|
|
<div class="tx-header">
|
|
<h2 class="tx-title">Transaction Details</h2>
|
|
</div>
|
|
|
|
<div id="tx_details_container" class="tx-content">
|
|
<div class="tx-card">
|
|
<!-- Status Header -->
|
|
<div class="tx-status-header">
|
|
<div
|
|
id="tx_status_indicator"
|
|
class="status-indicator"
|
|
></div>
|
|
<div class="status-details">
|
|
<h3 id="tx_status_title" class="status-title"></h3>
|
|
<p id="tx_status_subtext" class="status-subtext"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Transaction Info -->
|
|
<div class="tx-info-grid">
|
|
<!-- From/To Section -->
|
|
<div class="tx-address-section">
|
|
<div class="address-card">
|
|
<label class="address-label">From</label>
|
|
<sm-copy
|
|
id="tx_from"
|
|
class="address-value"
|
|
button-class="copy-button"
|
|
></sm-copy>
|
|
</div>
|
|
|
|
<div class="tx-arrow">→</div>
|
|
|
|
<div class="address-card">
|
|
<label class="address-label">To</label>
|
|
<sm-copy
|
|
id="tx_to"
|
|
class="address-value"
|
|
button-class="copy-button"
|
|
></sm-copy>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Transaction Hash -->
|
|
<div class="tx-hash-section">
|
|
<label class="section-label">Transaction Hash</label>
|
|
<sm-copy
|
|
id="tx_hash"
|
|
class="hash-value"
|
|
button-class="copy-button"
|
|
></sm-copy>
|
|
</div>
|
|
|
|
<!-- Metrics Grid -->
|
|
<div class="tx-metrics-grid">
|
|
<div class="metric-card">
|
|
<label class="metric-label">Amount</label>
|
|
<div id="tx_value" class="metric-value"></div>
|
|
</div>
|
|
|
|
<div class="metric-card">
|
|
<label class="metric-label">Transaction Fee</label>
|
|
<div id="tx_fee" class="metric-value"></div>
|
|
</div>
|
|
|
|
<div class="metric-card">
|
|
<label class="metric-label">Status</label>
|
|
<div id="tx_status" class="metric-value"></div>
|
|
</div>
|
|
|
|
<div class="metric-card">
|
|
<label class="metric-label">Timestamp</label>
|
|
<div id="tx_timestamp" class="metric-value"></div>
|
|
</div>
|
|
|
|
<div class="metric-card">
|
|
<label class="metric-label">Block</label>
|
|
<div id="tx_block" class="metric-value"></div>
|
|
</div>
|
|
|
|
<div class="metric-card">
|
|
<label class="metric-label">Gas Price</label>
|
|
<div id="tx_gasPrice" class="metric-value"></div>
|
|
</div>
|
|
|
|
<div class="metric-card">
|
|
<label class="metric-label">Gas Used</label>
|
|
<div id="tx_gasUsed" class="metric-value"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`
|
|
);
|
|
|
|
try {
|
|
// Fetch and display transaction details
|
|
const tx = await bscOperator.getTransactionDetails(txId);
|
|
|
|
if (!tx) {
|
|
renderElem(
|
|
getRef("tx_details_container"),
|
|
html`
|
|
<div class="error-state">
|
|
<div class="error-icon">!</div>
|
|
<h3>Transaction Not Found</h3>
|
|
<p>
|
|
The requested transaction could not be found on the
|
|
network.
|
|
</p>
|
|
<button
|
|
class="button"
|
|
onclick="getRef('tx_details').classList.add('hidden')"
|
|
>
|
|
Close
|
|
</button>
|
|
</div>
|
|
`
|
|
);
|
|
return;
|
|
}
|
|
|
|
updateTransactionDetails(tx);
|
|
} catch (error) {
|
|
console.error("Error fetching transaction details:", error);
|
|
renderElem(
|
|
getRef("tx_details_container"),
|
|
html`
|
|
<div class="error-state">
|
|
<div class="error-icon">⚠️</div>
|
|
<h3>Error Loading Transaction</h3>
|
|
<p>
|
|
We encountered an issue fetching the transaction details.
|
|
Please try again later.
|
|
</p>
|
|
<button
|
|
class="button"
|
|
onclick="getRef('tx_details').classList.add('hidden')"
|
|
>
|
|
Close
|
|
</button>
|
|
</div>
|
|
`
|
|
);
|
|
}
|
|
},
|
|
};
|
|
|
|
function checkQueryStringType(str) {
|
|
if (bscOperator.isValidAddress(str)) {
|
|
return "address";
|
|
} else if (/^0x([A-Fa-f0-9]{64})$/.test(str)) {
|
|
return "txid";
|
|
} else {
|
|
return "invalid";
|
|
}
|
|
}
|
|
|
|
function getFormattedTime(timestamp) {
|
|
try {
|
|
if (String(timestamp).length < 13) timestamp *= 1000;
|
|
let [day, month, date, year] = new Date(timestamp)
|
|
.toString()
|
|
.split(" "),
|
|
minutes = new Date(timestamp).getMinutes(),
|
|
hours = new Date(timestamp).getHours();
|
|
|
|
minutes = minutes < 10 ? `0${minutes}` : minutes;
|
|
const finalHours =
|
|
hours >= 12
|
|
? `${hours === 12 ? 12 : hours - 12}:${minutes} PM`
|
|
: `${hours === 0 ? 12 : hours}:${minutes} AM`;
|
|
|
|
return `${date} ${month} ${year}, ${finalHours}`;
|
|
} catch (e) {
|
|
console.error(e);
|
|
return timestamp;
|
|
}
|
|
}
|
|
function renderHome(state) {
|
|
getRef("page_container").dataset.page = "home";
|
|
renderElem(
|
|
getRef("page_container"),
|
|
html`
|
|
<aside
|
|
id="saved_addresses_wrapper"
|
|
class="flex flex-direction-column"
|
|
>
|
|
<h4>Searched addresses</h4>
|
|
<ul id="searched_addresses_list" class="grid gap-0-5"></ul>
|
|
</aside>
|
|
<section
|
|
id="balance_section"
|
|
class="grid gap-1-5"
|
|
style="width: min(32rem, 100%);"
|
|
>
|
|
|
|
<h2>Check BSC, USDC and USDT balance</h2>
|
|
<sm-form oninvalid="handleInvalidSearch()">
|
|
<div id="input_wrapper">
|
|
<sm-input
|
|
id="check_balance_input"
|
|
class="password-field flex-1"
|
|
placeholder="Enter BSC address, tx hash, or private key"
|
|
type="password"
|
|
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"
|
|
></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
|
|
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="multi-state-button">
|
|
<button
|
|
id="check_balance_button"
|
|
class="button button--primary h-100 w-100"
|
|
type="submit"
|
|
onclick=${() => checkBalance()}
|
|
disabled
|
|
>
|
|
Search
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</sm-form>
|
|
|
|
<div id="bsc_balance_wrapper" class="grid gap-2 hidden">
|
|
<div class="balance-card">
|
|
<div class="grid">
|
|
<div class="label">BSC address</div>
|
|
<sm-copy id="BSC_address"></sm-copy>
|
|
</div>
|
|
<div class="grid gap-1">
|
|
<h4>Balance</h4>
|
|
<ul
|
|
id="token_balances"
|
|
class="flex flex-direction-column gap-0-5"
|
|
>
|
|
<li class="flex align-center space-between">
|
|
<p>BNB</p>
|
|
<b id="Binance_balance" class="bsc-value"></b>
|
|
</li>
|
|
<li class="flex align-center space-between">
|
|
<p>USDC</p>
|
|
<b id="usdc_balance" class="bsc-value"></b>
|
|
</li>
|
|
<li class="flex align-center space-between">
|
|
<p>USDT</p>
|
|
<b id="usdt_balance" class="bsc-value"></b>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
id="address_transactions"
|
|
class="grid gap-2 hidden"
|
|
style="width: min(32rem, 100%);"
|
|
>
|
|
<div
|
|
class="flex flex-direction-column gap-0-5 sticky top-0"
|
|
style="background-color: rgba(var(--foreground-color), 1); z-index: 2; padding: 1rem 0; border-bottom: 1px solid rgba(var(--text-color), 0.1);"
|
|
>
|
|
<div class="flex align-center gap-0-5 space-between">
|
|
<h4>Transactions</h4>
|
|
<sm-chips
|
|
id="filter_selector"
|
|
onchange=${(e) =>
|
|
render.transactions(
|
|
getRef("check_balance_input").value
|
|
)}
|
|
>
|
|
<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>
|
|
</div>
|
|
<ul
|
|
id="transactions_list"
|
|
class="observe-empty-state grid gap-1"
|
|
></ul>
|
|
<div
|
|
class="empty-state align-self-center text-center margin-top-2"
|
|
>
|
|
<svg
|
|
class="icon"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
height="48px"
|
|
viewBox="0 0 24 24"
|
|
width="48px"
|
|
fill="rgba(var(--text-color), 0.5)"
|
|
>
|
|
<path d="M0 0h24v24H0V0z" fill="none" />
|
|
<path
|
|
d="M19.5 3.5L18 2l-1.5 1.5L15 2l-1.5 1.5L12 2l-1.5 1.5L9 2 7.5 3.5 6 2 4.5 3.5 3 2v20l1.5-1.5L6 22l1.5-1.5L9 22l1.5-1.5L12 22l1.5-1.5L15 22l1.5-1.5L18 22l1.5-1.5L21 22V2l-1.5 1.5zM19 19.09H5V4.91h14v14.18zM6 15h12v2H6zm0-4h12v2H6zm0-4h12v2H6z"
|
|
/>
|
|
</svg>
|
|
<p
|
|
class="margin-top-1"
|
|
style="color: rgba(var(--text-color), 0.7);"
|
|
>
|
|
Transactions will appear here
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<div id="tx_details" class="hidden"></div>
|
|
`
|
|
);
|
|
if (
|
|
window.ethereum &&
|
|
!(window.currentChainId && window.currentChainId === "0x38")
|
|
) {
|
|
renderError("Please switch MetaMask to Binance Mainnet");
|
|
}
|
|
renderSearchedAddressList();
|
|
}
|
|
function renderError(title, description) {
|
|
if (!title) title = "MetaMask not installed";
|
|
if (!description) description = "";
|
|
renderElem(
|
|
getRef("page_container"),
|
|
html`
|
|
<section id="error_section">
|
|
<svg
|
|
class="icon"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
xml:space="preserve"
|
|
id="Layer_1"
|
|
x="0"
|
|
y="0"
|
|
version="1.1"
|
|
viewBox="0 0 318.6 318.6"
|
|
>
|
|
<style>
|
|
.st1,
|
|
.st6 {
|
|
fill: #e4761b;
|
|
stroke: #e4761b;
|
|
stroke-linecap: round;
|
|
stroke-linejoin: round;
|
|
}
|
|
|
|
.st6 {
|
|
fill: #f6851b;
|
|
stroke: #f6851b;
|
|
}
|
|
</style>
|
|
<path
|
|
fill="#e2761b"
|
|
stroke="#e2761b"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="m274.1 35.5-99.5 73.9L193 65.8z"
|
|
/>
|
|
<path
|
|
d="m44.4 35.5 98.7 74.6-17.5-44.3zm193.9 171.3-26.5 40.6 56.7 15.6 16.3-55.3zm-204.4.9L50.1 263l56.7-15.6-26.5-40.6z"
|
|
class="st1"
|
|
/>
|
|
<path
|
|
d="m103.6 138.2-15.8 23.9 56.3 2.5-2-60.5zm111.3 0-39-34.8-1.3 61.2 56.2-2.5zM106.8 247.4l33.8-16.5-29.2-22.8zm71.1-16.5 33.9 16.5-4.7-39.3z"
|
|
class="st1"
|
|
/>
|
|
<path
|
|
fill="#d7c1b3"
|
|
stroke="#d7c1b3"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="m211.8 247.4-33.9-16.5 2.7 22.1-.3 9.3zm-105 0 31.5 14.9-.2-9.3 2.5-22.1z"
|
|
/>
|
|
<path
|
|
fill="#233447"
|
|
stroke="#233447"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="m138.8 193.5-28.2-8.3 19.9-9.1zm40.9 0 8.3-17.4 20 9.1z"
|
|
/>
|
|
<path
|
|
fill="#cd6116"
|
|
stroke="#cd6116"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="m106.8 247.4 4.8-40.6-31.3.9zM207 206.8l4.8 40.6 26.5-39.7zm23.8-44.7-56.2 2.5 5.2 28.9 8.3-17.4 20 9.1zm-120.2 23.1 20-9.1 8.2 17.4 5.3-28.9-56.3-2.5z"
|
|
/>
|
|
<path
|
|
fill="#e4751f"
|
|
stroke="#e4751f"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="m87.8 162.1 23.6 46-.8-22.9zm120.3 23.1-1 22.9 23.7-46zm-64-20.6-5.3 28.9 6.6 34.1 1.5-44.9zm30.5 0-2.7 18 1.2 45 6.7-34.1z"
|
|
/>
|
|
<path
|
|
d="m179.8 193.5-6.7 34.1 4.8 3.3 29.2-22.8 1-22.9zm-69.2-8.3.8 22.9 29.2 22.8 4.8-3.3-6.6-34.1z"
|
|
class="st6"
|
|
/>
|
|
<path
|
|
fill="#c0ad9e"
|
|
stroke="#c0ad9e"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="m180.3 262.3.3-9.3-2.5-2.2h-37.7l-2.3 2.2.2 9.3-31.5-14.9 11 9 22.3 15.5h38.3l22.4-15.5 11-9z"
|
|
/>
|
|
<path
|
|
fill="#161616"
|
|
stroke="#161616"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="m177.9 230.9-4.8-3.3h-27.7l-4.8 3.3-2.5 22.1 2.3-2.2h37.7l2.5 2.2z"
|
|
/>
|
|
<path
|
|
fill="#763d16"
|
|
stroke="#763d16"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="m278.3 114.2 8.5-40.8-12.7-37.9-96.2 71.4 37 31.3 52.3 15.3 11.6-13.5-5-3.6 8-7.3-6.2-4.8 8-6.1zM31.8 73.4l8.5 40.8-5.4 4 8 6.1-6.1 4.8 8 7.3-5 3.6 11.5 13.5 52.3-15.3 37-31.3-96.2-71.4z"
|
|
/>
|
|
<path
|
|
d="m267.2 153.5-52.3-15.3 15.9 23.9-23.7 46 31.2-.4h46.5zm-163.6-15.3-52.3 15.3-17.4 54.2h46.4l31.1.4-23.6-46zm71 26.4 3.3-57.7 15.2-41.1h-67.5l15 41.1 3.5 57.7 1.2 18.2.1 44.8h27.7l.2-44.8z"
|
|
class="st6"
|
|
/>
|
|
</svg>
|
|
<h2 id="error__title">${title}</h2>
|
|
<p>${description}</p>
|
|
</section>
|
|
`
|
|
);
|
|
}
|
|
function renderSearchedAddressList() {
|
|
compactIDB
|
|
.readAllData("contacts")
|
|
.then((contacts) => {
|
|
if (!getRef("searched_addresses_list")) return;
|
|
if (Object.keys(contacts).length === 0) {
|
|
renderElem(
|
|
getRef("searched_addresses_list"),
|
|
html`<li class="flex align-center justify-center">
|
|
<p>
|
|
Your searched addresses will appear here for easier access
|
|
in future.
|
|
</p>
|
|
</li>`
|
|
);
|
|
return;
|
|
}
|
|
const renderedContacts = [];
|
|
for (const contactKey in contacts) {
|
|
const { BSCAddress } = contacts[contactKey];
|
|
|
|
const isBSCAddress = contactKey.startsWith('0x') && bscOperator.isValidAddress(contactKey);
|
|
const floAddress = isBSCAddress ? null : contactKey;
|
|
|
|
if (floAddress && (floAddress === 'null' || floAddress === 'undefined' || floAddress.trim() === 'null')) {
|
|
continue;
|
|
}
|
|
|
|
const displayAddress = floAddress || BSCAddress;
|
|
|
|
// Create local copies to ensure proper closure capture
|
|
const localFloAddress = floAddress;
|
|
const localBSCAddress = BSCAddress;
|
|
const localContactKey = contactKey;
|
|
|
|
renderedContacts.push(html` <li
|
|
class="contact"
|
|
.dataset=${{ floAddress: localContactKey, BSCAddress: localBSCAddress }}
|
|
>
|
|
${localFloAddress
|
|
? html`
|
|
<sm-chips>
|
|
<sm-chip
|
|
value=${localFloAddress}
|
|
selected
|
|
onclick=${(e) => {
|
|
const copyElement = e.target.closest(".contact").querySelector("sm-copy");
|
|
copyElement.value = localFloAddress;
|
|
|
|
// Update selected state visually
|
|
const smChips = e.target.parentElement;
|
|
smChips.querySelectorAll('sm-chip').forEach(chip => chip.removeAttribute('selected'));
|
|
e.target.setAttribute('selected', '');
|
|
}}
|
|
>${localFloAddress.startsWith("F")
|
|
? "FLO"
|
|
: "BTC"}</sm-chip
|
|
>
|
|
<sm-chip
|
|
value=${localBSCAddress}
|
|
onclick=${(e) => {
|
|
const copyElement = e.target.closest(".contact").querySelector("sm-copy");
|
|
copyElement.value = localBSCAddress;
|
|
|
|
// Update selected state visually
|
|
const smChips = e.target.parentElement;
|
|
smChips.querySelectorAll('sm-chip').forEach(chip => chip.removeAttribute('selected'));
|
|
e.target.setAttribute('selected', '');
|
|
}}
|
|
>BSC</sm-chip>
|
|
</sm-chips>
|
|
`
|
|
: html``}
|
|
<sm-copy value="${displayAddress}"></sm-copy>
|
|
<div class="flex align-center space-between gap-0-5">
|
|
<button
|
|
class="button button--small"
|
|
onclick=${() => deleteContact(localContactKey)}
|
|
>
|
|
Delete
|
|
</button>
|
|
<button
|
|
class="button button--colored button--small"
|
|
onclick=${() => {
|
|
checkBalance(localBSCAddress, localFloAddress);
|
|
}}
|
|
>
|
|
Check balance
|
|
</button>
|
|
</div>
|
|
</li>`);
|
|
}
|
|
renderElem(
|
|
getRef("searched_addresses_list"),
|
|
html`${renderedContacts}`
|
|
);
|
|
})
|
|
.catch((error) => {
|
|
console.error(error);
|
|
});
|
|
}
|
|
|
|
function renderTdx(state) {
|
|
getRef("page_container").dataset.page = "tdx";
|
|
|
|
const txId = state.wildcards[0];
|
|
|
|
if (!txId || !/^0x([A-Fa-f0-9]{64})$/.test(txId)) {
|
|
notify("Invalid transaction hash", "error");
|
|
router.routeTo("#/balance");
|
|
return;
|
|
}
|
|
|
|
renderElem(
|
|
getRef("page_container"),
|
|
html`
|
|
<section class="tx-details-container">
|
|
<div class="tx-header">
|
|
<h2 class="tx-title">Transaction Details</h2>
|
|
|
|
${currentCurrency !== "bsc" && isHistoricApiAvailable
|
|
? html`
|
|
<div class="margin-left-1">
|
|
<sm-switch
|
|
id="tx_valuation_toggle"
|
|
?checked=${showCurrentValue}
|
|
>
|
|
<p slot="left" class="margin-right-0-5">
|
|
Show current value
|
|
</p>
|
|
</sm-switch>
|
|
</div>
|
|
`
|
|
: ""}
|
|
</div>
|
|
|
|
<div id="tx_details_container" class="tx-content">
|
|
<div class="loading-state" style="display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 300px; gap: 1rem;">
|
|
<sm-spinner size="lg"></sm-spinner>
|
|
<p class="loading-text">Fetching transaction details...</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
`
|
|
);
|
|
|
|
// Fetch and display transaction details
|
|
bscOperator
|
|
.getTransactionDetails(txId)
|
|
.then(async (tx) => {
|
|
if (!tx) {
|
|
renderNotFound();
|
|
return;
|
|
}
|
|
|
|
// Store the address for back navigation
|
|
window.lastViewedAddress = tx.from;
|
|
|
|
const value = tx.value
|
|
? ethers.utils.formatEther(ethers.BigNumber.from(tx.value._hex))
|
|
: "0";
|
|
const gasPrice = tx.gasPrice
|
|
? ethers.utils.formatUnits(
|
|
ethers.BigNumber.from(tx.gasPrice._hex),
|
|
"gwei"
|
|
)
|
|
: "N/A";
|
|
const gasUsed = tx.gasUsed
|
|
? ethers.utils.formatUnits(ethers.BigNumber.from(tx.gasUsed._hex))
|
|
: "Pending";
|
|
const timestamp = tx.timeStamp
|
|
? getFormattedTime(tx.timeStamp)
|
|
: "Pending";
|
|
const status = tx.blockNumber ? "confirmed" : "pending";
|
|
|
|
const fee =
|
|
tx.gasUsed && tx.gasPrice
|
|
? ethers.utils.formatEther(
|
|
ethers.BigNumber.from(tx.gasUsed._hex).mul(
|
|
ethers.BigNumber.from(tx.gasPrice._hex)
|
|
)
|
|
)
|
|
: "Pending";
|
|
|
|
renderElem(
|
|
getRef("tx_details_container"),
|
|
html`
|
|
<div class="tx-card" id="tx-status">
|
|
<!-- Status Header -->
|
|
<div class="tx-status-header" style="display: flex; justify-content: space-between; align-items: center; gap: 0.5rem;">
|
|
<div style="display: flex; align-items: center; gap: 1rem; flex: 1; min-width: 0;">
|
|
<div
|
|
id="tx_status_indicator"
|
|
class="status-indicator"
|
|
style="flex-shrink: 0;"
|
|
></div>
|
|
<div class="status-details" style="min-width: 0;">
|
|
<h3 id="tx_status_title" class="status-title"></h3>
|
|
<p id="tx_status_subtext" class="status-subtext"></p>
|
|
</div>
|
|
</div>
|
|
<a id="tx_explorer_link" target="_blank" rel="noopener noreferrer" class="button button--primary" style="text-decoration: none; padding: 0.4rem 0.75rem; font-size: 0.8rem; white-space: nowrap; flex-shrink: 0;">
|
|
BSCScan
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Main Transaction Info -->
|
|
<div class="tx-info-grid">
|
|
<!-- From/To Section -->
|
|
<div class="tx-address-section">
|
|
<div class="address-card">
|
|
<label class="address-label">From</label>
|
|
<sm-copy id="tx_from" class="address-value"></sm-copy>
|
|
</div>
|
|
|
|
<div class="tx-arrow">→</div>
|
|
|
|
<div class="address-card">
|
|
<label class="address-label">To</label>
|
|
<sm-copy id="tx_to" class="address-value"></sm-copy>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Transaction Hash -->
|
|
<div class="tx-hash-section">
|
|
<label class="section-label">Transaction Hash</label>
|
|
<sm-copy id="tx_hash" class="hash-value"></sm-copy>
|
|
</div>
|
|
|
|
<!-- Metrics Grid -->
|
|
<div class="tx-metrics-grid">
|
|
<div class="metric-card">
|
|
<label class="metric-label">Value</label>
|
|
<div
|
|
id="tx_value"
|
|
class="metric-value bsc-value"
|
|
data-eth="${value}"
|
|
data-timestamp="${tx.timeStamp || ""}"
|
|
></div>
|
|
</div>
|
|
|
|
<div class="metric-card">
|
|
<label class="metric-label">Gas Price</label>
|
|
<div id="tx_gasPrice" class="metric-value"></div>
|
|
</div>
|
|
|
|
<div class="metric-card">
|
|
<label class="metric-label">Transaction Fee</label>
|
|
<div
|
|
id="tx_fee"
|
|
class="metric-value bsc-value"
|
|
data-eth="${fee}"
|
|
data-timestamp="${tx.timeStamp || ""}"
|
|
></div>
|
|
</div>
|
|
|
|
<div class="metric-card">
|
|
<label class="metric-label">Timestamp</label>
|
|
<div id="tx_timestamp" class="metric-value"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`
|
|
);
|
|
|
|
if (getRef("tx_status_indicator")) {
|
|
const statusIndicator = getRef("tx_status_indicator");
|
|
if (status === "confirmed") {
|
|
statusIndicator.style.backgroundColor = "var(--color-success)";
|
|
statusIndicator.style.boxShadow =
|
|
"0 0 0 4px rgba(var(--color-success-rgb), 0.2)";
|
|
} else {
|
|
statusIndicator.style.backgroundColor = "var(--color-warning)";
|
|
statusIndicator.style.boxShadow =
|
|
"0 0 0 4px rgba(var(--color-warning-rgb), 0.2)";
|
|
}
|
|
}
|
|
|
|
if (getRef("tx_status_title")) {
|
|
getRef("tx_status_title").textContent =
|
|
status === "confirmed"
|
|
? `${tx.confirmations || 0} Confirmations`
|
|
: "Transaction Pending";
|
|
}
|
|
|
|
if (getRef("tx_status_subtext")) {
|
|
getRef("tx_status_subtext").textContent =
|
|
status === "confirmed"
|
|
? `Included in Block #${tx.blockNumber}`
|
|
: "";
|
|
}
|
|
|
|
if (getRef("tx_status")) getRef("tx_status").value = status;
|
|
if (getRef("tx-status")) getRef("tx-status").value = tx.status;
|
|
if (getRef("tx_from")) {
|
|
getRef("tx_from").value = tx.from;
|
|
getRef("tx_from").addEventListener("click", function (e) {
|
|
if (!e.target.closest("sm-copy button")) {
|
|
const address = getRef("tx_from").value;
|
|
window.location.hash = `#/balance/${address}`;
|
|
checkBalance(address);
|
|
}
|
|
});
|
|
getRef("tx_from").style.cursor = "pointer";
|
|
getRef("tx_from").title = "View address details";
|
|
}
|
|
|
|
if (getRef("tx_to")) {
|
|
getRef("tx_to").value = tx.to || "Contract Creation";
|
|
if (tx.to) {
|
|
getRef("tx_to").addEventListener("click", function (e) {
|
|
if (!e.target.closest("sm-copy button")) {
|
|
const address = getRef("tx_to").value;
|
|
window.location.hash = `#/balance/${address}`;
|
|
checkBalance(address);
|
|
}
|
|
});
|
|
getRef("tx_to").style.cursor = "pointer";
|
|
getRef("tx_to").title = "View address details";
|
|
}
|
|
}
|
|
|
|
if (getRef("tx_hash")) getRef("tx_hash").value = tx.hash;
|
|
if (getRef("tx_explorer_link")) {
|
|
getRef("tx_explorer_link").href = `https://bscscan.com/tx/${tx.hash}`;
|
|
}
|
|
|
|
if (getRef("tx_value")) {
|
|
getRef("tx_value").dataset.bsc = value;
|
|
getRef("tx_value").dataset.timestamp = tx.timeStamp || "";
|
|
getRef("tx_value").classList.add("bsc-value");
|
|
getRef("tx_value").textContent = formatCurrency(value);
|
|
}
|
|
|
|
if (getRef("tx_gasPrice"))
|
|
getRef("tx_gasPrice").textContent = `${gasPrice} Gwei`;
|
|
if (getRef("tx_fee")) {
|
|
getRef("tx_fee").dataset.bsc = fee;
|
|
getRef("tx_fee").dataset.timestamp = tx.timeStamp || "";
|
|
getRef("tx_fee").classList.add("bsc-value");
|
|
getRef("tx_fee").textContent = formatCurrency(fee);
|
|
}
|
|
if (getRef("tx_timestamp"))
|
|
getRef("tx_timestamp").textContent = timestamp;
|
|
|
|
if (getRef("tx_valuation_toggle")) {
|
|
// Clear any previous event listeners by replacing with a fresh element
|
|
const toggle = getRef("tx_valuation_toggle");
|
|
const toggleParent = toggle.parentElement;
|
|
const newToggle = toggle.cloneNode(true);
|
|
|
|
// Add our event listener to the new element before inserting it
|
|
newToggle.addEventListener("change", async (e) => {
|
|
showCurrentValue = e.target.checked;
|
|
localStorage.setItem(
|
|
"eth-wallet-show-current-value",
|
|
showCurrentValue
|
|
);
|
|
|
|
// Get elements directly to ensure we update them
|
|
const valueElement = getRef("tx_value");
|
|
const feeElement = getRef("tx_fee");
|
|
|
|
if (valueElement && valueElement.dataset.eth) {
|
|
const ethAmount = parseFloat(valueElement.dataset.eth);
|
|
const timestamp = valueElement.dataset.timestamp;
|
|
valueElement.textContent = await getConvertedAmount(
|
|
ethAmount,
|
|
timestamp
|
|
);
|
|
}
|
|
|
|
if (feeElement && feeElement.dataset.eth) {
|
|
const ethFee = parseFloat(feeElement.dataset.eth);
|
|
const timestamp = feeElement.dataset.timestamp;
|
|
feeElement.textContent = await getConvertedAmount(
|
|
ethFee,
|
|
timestamp
|
|
);
|
|
}
|
|
});
|
|
|
|
// Replace the original toggle with our new one
|
|
toggleParent.replaceChild(newToggle, toggle);
|
|
}
|
|
convertAllDisplayedCurrency();
|
|
if (currentCurrency !== "bsc" && !isHistoricApiAvailable) {
|
|
testHistoricalAPIAndRenderToggle();
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error("Error:", error);
|
|
renderErrorState();
|
|
});
|
|
|
|
// Helper functions
|
|
function renderNotFound() {
|
|
renderElem(
|
|
getRef("tx_details_container"),
|
|
html`
|
|
<div class="error-state">
|
|
<div class="error-icon">!</div>
|
|
<h3>Transaction Not Found</h3>
|
|
<p>
|
|
The requested transaction could not be found on the network.
|
|
</p>
|
|
<sm-button onclick="router.routeTo('#/balance')">
|
|
Back to Balance
|
|
</sm-button>
|
|
</div>
|
|
`
|
|
);
|
|
}
|
|
|
|
function renderErrorState() {
|
|
renderElem(
|
|
getRef("tx_details_container"),
|
|
html`
|
|
<div class="error-state">
|
|
<div class="error-icon">⚠️</div>
|
|
<h3>Error Loading Transaction</h3>
|
|
<p>
|
|
We encountered an issue fetching the transaction details.
|
|
Please try again later.
|
|
</p>
|
|
<sm-button
|
|
variant="primary"
|
|
onclick="router.routeTo('#/balance')"
|
|
class="back-button"
|
|
>
|
|
Back to Balance
|
|
</sm-button>
|
|
</div>
|
|
`
|
|
);
|
|
}
|
|
}
|
|
|
|
// Function to update transaction details toggle
|
|
function updateTransactionDetailsToggle() {
|
|
if (getRef("page_container")?.dataset?.page !== "tdx") return;
|
|
|
|
const toggleContainer = document.querySelector(".tx-header");
|
|
if (!toggleContainer) return;
|
|
|
|
const existingToggle =
|
|
toggleContainer.querySelector(".margin-left-auto");
|
|
|
|
if (currentCurrency === "bsc") {
|
|
if (existingToggle) existingToggle.remove();
|
|
return;
|
|
}
|
|
|
|
if (
|
|
currentCurrency !== "bsc" &&
|
|
isHistoricApiAvailable &&
|
|
!existingToggle
|
|
) {
|
|
const toggleDiv = document.createElement("div");
|
|
toggleDiv.className = "margin-left-auto";
|
|
toggleDiv.innerHTML = `
|
|
<sm-switch id="tx_valuation_toggle" ${showCurrentValue ? "checked" : ""}>
|
|
<p slot="left" class="margin-right-0-5">
|
|
Show current value
|
|
</p>
|
|
</sm-switch>
|
|
`;
|
|
toggleContainer.appendChild(toggleDiv);
|
|
|
|
const toggle = toggleDiv.querySelector("sm-switch");
|
|
toggle.addEventListener("change", async (e) => {
|
|
showCurrentValue = e.target.checked;
|
|
localStorage.setItem(
|
|
"bsc-wallet-show-current-value",
|
|
showCurrentValue
|
|
);
|
|
|
|
const valueElement = getRef("tx_value");
|
|
const feeElement = getRef("tx_fee");
|
|
|
|
if (valueElement && valueElement.dataset.bsc) {
|
|
const bscAmount = parseFloat(valueElement.dataset.bsc);
|
|
const timestamp = valueElement.dataset.timestamp;
|
|
valueElement.textContent = await getConvertedAmount(
|
|
bscAmount,
|
|
timestamp
|
|
);
|
|
}
|
|
|
|
if (feeElement && feeElement.dataset.bsc) {
|
|
const bscFee = parseFloat(feeElement.dataset.bsc);
|
|
const timestamp = feeElement.dataset.timestamp;
|
|
feeElement.textContent = await getConvertedAmount(
|
|
bscFee,
|
|
timestamp
|
|
);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function checkBalance(BSCAddress, floAddress) {
|
|
if (window.checkingBalance) return;
|
|
|
|
if (!BSCAddress) {
|
|
let keyToConvert = document
|
|
.querySelector("#check_balance_input")
|
|
.value.trim();
|
|
|
|
if (/^0x[0-9a-fA-F]{64}$/.test(keyToConvert)) {
|
|
window.location.hash = `#/tdx/${keyToConvert}`;
|
|
return;
|
|
} else if (bscOperator.isValidAddress(keyToConvert)) {
|
|
BSCAddress = keyToConvert;
|
|
} else {
|
|
// Handle raw hex private key (BSC)
|
|
if (/^[0-9a-fA-F]{64}$/.test(keyToConvert)) {
|
|
// Raw hex is BSC private key, just derive BSC address
|
|
BSCAddress = floEthereum.ethAddressFromPrivateKey(keyToConvert);
|
|
floAddress = null;
|
|
}
|
|
// Handle FLO private key (starts with R)
|
|
else if (keyToConvert.startsWith("R")) {
|
|
const ethPrivateKey = coinjs.wif2privkey(keyToConvert).privkey;
|
|
BSCAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey);
|
|
floAddress = floCrypto.getFloID(keyToConvert);
|
|
}
|
|
// Handle BTC private key (starts with L or K)
|
|
else if (keyToConvert.startsWith("L") || keyToConvert.startsWith("K")) {
|
|
const ethPrivateKey = coinjs.wif2privkey(keyToConvert).privkey;
|
|
BSCAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey);
|
|
floAddress = btcOperator.bech32Address(keyToConvert);
|
|
}
|
|
else {
|
|
const ethPrivateKey = coinjs.wif2privkey(keyToConvert).privkey;
|
|
BSCAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey);
|
|
floAddress = keyToConvert.startsWith("R")
|
|
? floCrypto.getFloID(keyToConvert)
|
|
: btcOperator.bech32Address(keyToConvert);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!BSCAddress) return;
|
|
|
|
window.checkingBalance = true;
|
|
|
|
// Store the address for navigation context
|
|
window.lastViewedAddress = BSCAddress;
|
|
|
|
// Update URL without triggering a page reload
|
|
window.location.hash = `#/balance/${BSCAddress}`;
|
|
|
|
buttonLoader("check_balance_button", true);
|
|
Promise.all([
|
|
bscOperator.getBalance(BSCAddress),
|
|
bscOperator.getTokenBalance(BSCAddress, "usdc"),
|
|
bscOperator.getTokenBalance(BSCAddress, "usdt"),
|
|
])
|
|
.then(([BinanceBalance, usdcBalance, usdtBalance]) => {
|
|
// First check if any contact with this BSC address already exists
|
|
compactIDB.readAllData("contacts").then((allContacts) => {
|
|
const existingContact = Object.entries(allContacts).find(
|
|
([key, contact]) => contact.BSCAddress === BSCAddress
|
|
);
|
|
|
|
// If BSC address already exists in any contact, don't add again
|
|
if (existingContact) {
|
|
renderSearchedAddressList();
|
|
return;
|
|
}
|
|
|
|
compactIDB
|
|
.addData(
|
|
"contacts",
|
|
{
|
|
BSCAddress,
|
|
},
|
|
floAddress || BSCAddress
|
|
)
|
|
.then(() => {
|
|
renderSearchedAddressList();
|
|
})
|
|
.catch((error) => {
|
|
console.error(error);
|
|
});
|
|
});
|
|
renderElem(
|
|
getRef("bsc_balance_wrapper"),
|
|
html`
|
|
<div class="grid">
|
|
<div class="label">BSC address</div>
|
|
<sm-copy id="BSC_address" value="${BSCAddress}"></sm-copy>
|
|
</div>
|
|
${floAddress && floAddress !== BSCAddress
|
|
? html`
|
|
<div class="grid">
|
|
<div class="label">
|
|
${floAddress.startsWith("F") ? "FLO" : "BTC"} address
|
|
</div>
|
|
<sm-copy
|
|
id="flo_address"
|
|
value="${floAddress}"
|
|
></sm-copy>
|
|
</div>
|
|
`
|
|
: ""}
|
|
<div class="grid gap-1">
|
|
<h4>Balance</h4>
|
|
<ul
|
|
id="bsc_address_balance"
|
|
class="flex flex-direction-column gap-0-5"
|
|
>
|
|
<li class="flex align-center space-between">
|
|
<p>Binance</p>
|
|
<b
|
|
id="Binance_balance"
|
|
class="bsc-value"
|
|
data-bsc="${BinanceBalance}"
|
|
>${formatCurrency(BinanceBalance)}</b
|
|
>
|
|
</li>
|
|
<li class="flex align-center space-between">
|
|
<p>USDC</p>
|
|
<b
|
|
id="usdc_balance"
|
|
class="bsc-value"
|
|
data-bsc="${usdcBalance}"
|
|
>${usdcBalance} USDC</b
|
|
>
|
|
</li>
|
|
<li class="flex align-center space-between">
|
|
<p>USDT</p>
|
|
<b
|
|
id="usdt_balance"
|
|
class="bsc-value"
|
|
data-bsc="${usdtBalance}"
|
|
>${usdtBalance} USDT</b
|
|
>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
`
|
|
);
|
|
getRef("bsc_balance_wrapper").classList.remove("hidden");
|
|
getRef("bsc_balance_wrapper").animate(
|
|
[
|
|
{
|
|
transform: "translateY(-1rem)",
|
|
opacity: 0,
|
|
},
|
|
{
|
|
transform: "none",
|
|
opacity: 1,
|
|
},
|
|
],
|
|
{
|
|
easing: "ease",
|
|
duration: 300,
|
|
fill: "forwards",
|
|
}
|
|
);
|
|
// Show transaction history after balance is shown
|
|
render.transactions(BSCAddress);
|
|
})
|
|
.catch((error) => {
|
|
notify(error, "error");
|
|
})
|
|
.finally(() => {
|
|
buttonLoader("check_balance_button", false);
|
|
|
|
window.checkingBalance = false;
|
|
});
|
|
}
|
|
|
|
function handleInvalidSearch() {
|
|
if (document.startViewTransition) {
|
|
document.startViewTransition(() => {
|
|
getRef("bsc_balance_wrapper").classList.add("hidden");
|
|
getRef("address_transactions").classList.add("hidden");
|
|
// Clear the search input
|
|
getRef("check_balance_input").value = "";
|
|
});
|
|
} else {
|
|
getRef("bsc_balance_wrapper").classList.add("hidden");
|
|
getRef("address_transactions").classList.add("hidden");
|
|
// Clear the search input
|
|
getRef("check_balance_input").value = "";
|
|
}
|
|
}
|
|
async function deleteContact(floAddress) {
|
|
const confirmed = await getConfirmation("Delete contact", {
|
|
message: "Are you sure you want to delete this contact?",
|
|
});
|
|
if (!confirmed) return;
|
|
compactIDB
|
|
.removeData("contacts", floAddress)
|
|
.then(() => {
|
|
renderSearchedAddressList();
|
|
})
|
|
.catch((error) => {
|
|
console.error(error);
|
|
});
|
|
}
|
|
|
|
router.addRoute("send", (state) => {
|
|
getRef("page_container").dataset.page = "send";
|
|
renderElem(
|
|
getRef("page_container"),
|
|
html`
|
|
<sm-form id="send_tx_form" style="width: min(32rem, 100%)">
|
|
<fieldset class="flex flex-direction-column gap-0-5">
|
|
<div class="flex space-between align-center">
|
|
<div class="flex flex-direction-column gap-0-5">
|
|
<h4>Sender</h4>
|
|
<p>
|
|
Amount will be deducted from equivalent Binance address
|
|
</p>
|
|
</div>
|
|
<button
|
|
id="check_balance_button"
|
|
class="button button--small button--colored"
|
|
onclick="checkSenderBalance()"
|
|
disabled
|
|
>
|
|
Check balance
|
|
</button>
|
|
</div>
|
|
|
|
<sm-input
|
|
id="private_key_input"
|
|
placeholder="Sender's FLO/BTC/BSC private key"
|
|
oninput=${handleSenderInput}
|
|
data-private-key
|
|
class="password-field"
|
|
type="password"
|
|
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>
|
|
|
|
<div
|
|
id="sender_balance_container"
|
|
class="flex align-center gap-0-3 hidden"
|
|
></div>
|
|
</fieldset>
|
|
<fieldset class="flex flex-direction-column gap-1">
|
|
<div class="flex flex-direction-column gap-0-5">
|
|
<h4>Receiver</h4>
|
|
<div class="grid gap-0-5">
|
|
<sm-input
|
|
class="receiver-address"
|
|
placeholder="Receiver's Binance address"
|
|
data-BSC-address
|
|
animate
|
|
required
|
|
></sm-input>
|
|
<div class="flex flex-direction-column gap-0-5">
|
|
<sm-input
|
|
class="receiver-amount amount-shown flex-1"
|
|
placeholder="Amount"
|
|
type="number"
|
|
step="0.000001"
|
|
min="0.000001"
|
|
error-text="Amount should be grater than 0.000001 Binance"
|
|
animate
|
|
required
|
|
>
|
|
<div class="asset-symbol flex" slot="icon">
|
|
<svg
|
|
hight="34"
|
|
width="34"
|
|
version="1.0"
|
|
id="katman_1"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
x="0px"
|
|
y="0px"
|
|
viewBox="0 0 800 600"
|
|
style="enable-background:new 0 0 800 600;"
|
|
xml:space="preserve"
|
|
>
|
|
<style type="text/css">
|
|
.st0 {
|
|
fill-rule: evenodd;
|
|
clip-rule: evenodd;
|
|
fill: #f3ba2f;
|
|
}
|
|
.st1 {
|
|
fill-rule: evenodd;
|
|
clip-rule: evenodd;
|
|
fill: #131415;
|
|
}
|
|
</style>
|
|
<g id="Light">
|
|
<g
|
|
id="OneArt-_x2022_-Desktop-_x2022_-Light"
|
|
transform="translate(-457.000000, -1515.000000)"
|
|
>
|
|
<g
|
|
id="Block"
|
|
transform="translate(41.000000, 1263.000000)"
|
|
>
|
|
<g
|
|
id="TVL"
|
|
transform="translate(48.000000, 252.000000)"
|
|
>
|
|
<g
|
|
id="Icons_x2F_Icon-24_x2F_cake"
|
|
transform="translate(368.000000, 0.000000)"
|
|
>
|
|
<circle
|
|
id="Oval"
|
|
class="st0"
|
|
cx="399.8"
|
|
cy="299.6"
|
|
r="230.7"
|
|
/>
|
|
<g
|
|
id="Icons_x2F_icon-24_x2F_networks_x2F_binance_x5F_smart_x5F_chain"
|
|
transform="translate(3.333333, 3.333333)"
|
|
>
|
|
<path
|
|
id="Combined-Shape"
|
|
class="st1"
|
|
d="M456.3,320.8l34.8,34.7L396.5,450L302,355.5l34.8-34.7l59.7,59.7L456.3,320.8z
|
|
M396.5,261l35.3,35.3h0l0,0l-35.3,35.3l-35.2-35.2l0-0.1l0,0l6.2-6.2l3-3L396.5,261z M277.5,261.5l34.8,34.8L277.5,331
|
|
l-34.8-34.8L277.5,261.5z M515.5,261.5l34.8,34.8L515.5,331l-34.8-34.8L515.5,261.5z M396.5,142.5L491,237l-34.8,34.8
|
|
L396.5,212l-59.7,59.7L302,237L396.5,142.5z"
|
|
/>
|
|
</g>
|
|
</g>
|
|
</g>
|
|
</g>
|
|
</g>
|
|
</g>
|
|
</svg>
|
|
</div>
|
|
</sm-input>
|
|
<sm-chips
|
|
id="asset_selector"
|
|
onchange=${handleAssetChange}
|
|
>
|
|
<sm-chip value="Binance" selected>Binance</sm-chip>
|
|
<sm-chip value="usdc">USDC</sm-chip>
|
|
<sm-chip value="usdt">USDT</sm-chip>
|
|
</sm-chips>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="multi-state-button">
|
|
<button
|
|
id="send_tx_button"
|
|
class="button button--primary"
|
|
type="submit"
|
|
disabled
|
|
onclick="sendTx()"
|
|
>
|
|
Send Binance
|
|
</button>
|
|
</div>
|
|
</fieldset>
|
|
</sm-form>
|
|
`
|
|
);
|
|
if (
|
|
window.ethereum &&
|
|
!(window.currentChainId && window.currentChainId === "0x38")
|
|
) {
|
|
renderError("Please switch MetaMask to Binance Mainnet");
|
|
}
|
|
});
|
|
|
|
// function togglePrivateKeyVisibility(input) {
|
|
// const target = input.closest("sm-input") ;
|
|
// target.type = target.type === "password" ? "text" : "password";
|
|
// target.focusIn();
|
|
// }
|
|
|
|
function togglePrivateKeyVisibility(input) {
|
|
const target = input.closest("sm-input");
|
|
target.type = target.type === "password" ? "text" : "password";
|
|
}
|
|
|
|
function checkSenderBalance() {
|
|
let address;
|
|
const privateKey = getRef("private_key_input").value.trim();
|
|
|
|
if (!privateKey) {
|
|
return notify(`Please enter sender's private key to check balance`);
|
|
}
|
|
|
|
if (
|
|
privateKey.startsWith("R") ||
|
|
privateKey.startsWith("L") ||
|
|
privateKey.startsWith("K")
|
|
) {
|
|
address = floEthereum.ethAddressFromPrivateKey(
|
|
coinjs.wif2privkey(privateKey).privkey
|
|
);
|
|
} else {
|
|
address = floEthereum.ethAddressFromPrivateKey(privateKey);
|
|
}
|
|
|
|
getRef("sender_balance_container").classList.remove("hidden");
|
|
renderElem(
|
|
getRef("sender_balance_container"),
|
|
html` Loading balance...<sm-spinner></sm-spinner> `
|
|
);
|
|
|
|
const selectedAsset = getRef("asset_selector").value;
|
|
|
|
const promises = [bscOperator.getBalance(address)];
|
|
if (selectedAsset !== "Binance") {
|
|
promises.push(bscOperator.getTokenBalance(address, selectedAsset));
|
|
}
|
|
|
|
Promise.all(promises)
|
|
.then(([ethBalance, tokenBalance]) => {
|
|
renderElem(
|
|
getRef("sender_balance_container"),
|
|
html`
|
|
<div
|
|
class="grid gap-1 w-100"
|
|
style="padding: 1rem; border-radius: 0.5rem; border: solid thin rgba(var(--text-color),0.3)"
|
|
>
|
|
<div class="grid">
|
|
<p class="label">Sender address</p>
|
|
<sm-copy value=${address}
|
|
><p>${address}</p>
|
|
<p></p
|
|
></sm-copy>
|
|
</div>
|
|
<p>
|
|
Balance:
|
|
<b class="amount-shown">${ethBalance} BSC</b>
|
|
${selectedAsset !== "Binance"
|
|
? html`|<b class="amount-shown"
|
|
>${tokenBalance} ${selectedAsset.toUpperCase()}</b
|
|
>`
|
|
: ""}
|
|
</p>
|
|
</div>
|
|
`
|
|
);
|
|
})
|
|
.catch((err) => {
|
|
notify(err, "error");
|
|
});
|
|
}
|
|
function handleSenderInput(e) {
|
|
getRef("check_balance_button").disabled = false;
|
|
if (!e.target.isValid) {
|
|
// getRef("sender_balance_container").classList.add("hidden");
|
|
}
|
|
}
|
|
function handleAssetChange(e) {
|
|
const asset = e.target.value;
|
|
const amountInput =
|
|
getRef("send_tx_form").querySelector(".receiver-amount");
|
|
amountInput.value = "";
|
|
amountInput.setAttribute(
|
|
"error-text",
|
|
`Amount should be grater than 0.000001 ${asset.toUpperCase()}`
|
|
);
|
|
document.querySelectorAll(".asset-symbol").forEach((elem) => {
|
|
elem.innerHTML = assetIcons[asset];
|
|
});
|
|
getRef("send_tx_button").textContent = `Send ${asset.toUpperCase()}`;
|
|
}
|
|
async function sendTx() {
|
|
const receiver = getRef("send_tx_form").querySelector(".receiver-address").value.trim();
|
|
const amount = getRef("send_tx_form").querySelector(".receiver-amount").value.trim();
|
|
const asset = getRef("asset_selector").value;
|
|
|
|
try {
|
|
const confirmation = await getConfirmation("Send transaction", {
|
|
message: `You are about to send ${amount} ${asset.toUpperCase()} to ${receiver}`,
|
|
confirmText: "Send",
|
|
});
|
|
|
|
if (!confirmation) {
|
|
return;
|
|
}
|
|
|
|
buttonLoader("send_tx_button", true);
|
|
let privateKey = getRef("private_key_input").value.trim();
|
|
|
|
if (/^[0-9a-fA-F]{64}$/.test(privateKey)) {
|
|
privateKey = coinjs.privkey2wif(privateKey);
|
|
}
|
|
privateKey = coinjs.wif2privkey(privateKey).privkey;
|
|
|
|
switch (asset) {
|
|
case "Binance": {
|
|
const tx = await bscOperator.sendTransaction({
|
|
privateKey,
|
|
receiver,
|
|
amount,
|
|
});
|
|
showTransactionResult("pending", { txHash: tx.hash });
|
|
await tx.wait();
|
|
showTransactionResult("confirmed", { txHash: tx.hash });
|
|
break;
|
|
}
|
|
case "usdc":
|
|
case "usdt": {
|
|
const tx = await bscOperator.sendToken({
|
|
privateKey,
|
|
receiver,
|
|
amount,
|
|
token: asset,
|
|
});
|
|
showTransactionResult("pending", { txHash: tx.hash });
|
|
await tx.wait();
|
|
showTransactionResult("confirmed", { txHash: tx.hash });
|
|
break;
|
|
}
|
|
}
|
|
getRef("send_tx_form").reset();
|
|
} catch (e) {
|
|
showTransactionResult("failed", {
|
|
description: `Insufficient ${asset.toUpperCase()} balance`,
|
|
});
|
|
const regex = /\(error=({.*?}),/;
|
|
const match = e.message.match(regex);
|
|
if (match && match[1]) {
|
|
const { code } = JSON.parse(match[1]);
|
|
if (code === -32000) {
|
|
showTransactionResult("failed", {
|
|
description: `Insufficient ${asset.toUpperCase()} balance`,
|
|
});
|
|
} else {
|
|
showTransactionResult("failed", { description: e.message });
|
|
}
|
|
}
|
|
} finally {
|
|
buttonLoader("send_tx_button", false);
|
|
}
|
|
}
|
|
|
|
function showTransactionResult(status, { txHash, description = "" }) {
|
|
switch (status) {
|
|
case "pending":
|
|
renderElem(
|
|
getRef("transaction_result_popup__content"),
|
|
html`
|
|
<ul>
|
|
<li class="transaction__phase">
|
|
<svg
|
|
class="icon confirmed"
|
|
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>
|
|
<h4>Transaction sent</h4>
|
|
</li>
|
|
<li class="transaction__phase">
|
|
<sm-spinner></sm-spinner>
|
|
<p>Waiting for transaction to be confirmed</p>
|
|
</li>
|
|
</ul>
|
|
<div class="grid">
|
|
<span class="label">Transaction ID</span>
|
|
<sm-copy value=${txHash}></sm-copy>
|
|
</div>
|
|
<a
|
|
class="button button--primary"
|
|
target="_blank"
|
|
href=${`https://bscscan.com/tx/${txHash}`}
|
|
>Check transaction status</a
|
|
>
|
|
`
|
|
);
|
|
break;
|
|
case "confirmed":
|
|
renderElem(
|
|
getRef("transaction_result_popup__content"),
|
|
html`
|
|
<svg
|
|
class="icon user-action-result__icon confirmed"
|
|
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 confirmed</h4>
|
|
<p>Transaction has been confirmed on the blockchain.</p>
|
|
</div>
|
|
<div class="grid">
|
|
<span class="label">Transaction ID</span>
|
|
<sm-copy value=${txHash}></sm-copy>
|
|
</div>
|
|
<a
|
|
class="button button--primary"
|
|
target="_blank"
|
|
href=${`https://bscscan.com/tx/${txHash}`}
|
|
>Check transaction status</a
|
|
>
|
|
`
|
|
);
|
|
break;
|
|
case "failed":
|
|
renderElem(
|
|
getRef("transaction_result_popup__content"),
|
|
html`
|
|
<svg
|
|
class="icon user-action-result__icon failed"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
height="24px"
|
|
viewBox="0 0 24 24"
|
|
width="24px"
|
|
fill="#000000"
|
|
>
|
|
<path
|
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"
|
|
/>
|
|
</svg>
|
|
<div class="grid gap-0-5 justify-center text-center">
|
|
<h4>Transaction failed</h4>
|
|
<p>${description}</p>
|
|
</div>
|
|
`
|
|
);
|
|
break;
|
|
}
|
|
openPopup("transaction_result_popup");
|
|
}
|
|
|
|
router.addRoute("create", (state) => {
|
|
getRef("page_container").dataset.page = "create";
|
|
renderElem(
|
|
getRef("page_container"),
|
|
html`
|
|
<div class="grid gap-1">
|
|
<h2>Don't have a Binance address? Create one</h2>
|
|
<section class="create-buttons">
|
|
<button
|
|
class="button button--primary interactive gap-0-5 margin-right-auto"
|
|
onclick=${generateNewID}
|
|
>
|
|
<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 BSC address
|
|
</button>
|
|
<button
|
|
class="button button--primary interactive gap-0-5 margin-right-auto"
|
|
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 BSC address
|
|
</button>
|
|
</section>
|
|
</div>
|
|
<div id="created_address_wrapper" class="grid gap-1"></div>
|
|
`
|
|
);
|
|
});
|
|
function generateNewID() {
|
|
const { floID, privKey } = floCrypto.generateNewID();
|
|
const ethPrivateKey = coinjs.wif2privkey(privKey).privkey;
|
|
const BSCAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey);
|
|
const btcPrivKey = btcOperator.convert.wif(privKey);
|
|
const btcAddr = btcOperator.bech32Address(btcPrivKey);
|
|
renderElem(
|
|
getRef("created_address_wrapper"),
|
|
html`
|
|
<ul id="generated_addresses" class="grid gap-1-5">
|
|
<li class="grid gap-0-5">
|
|
<div>
|
|
<h5>Binance Address</h5>
|
|
<sm-copy value="${BSCAddress}"></sm-copy>
|
|
</div>
|
|
<div>
|
|
<h5>Private Key</h5>
|
|
<sm-copy value="${ethPrivateKey}"></sm-copy>
|
|
</div>
|
|
</li>
|
|
<li class="grid gap-0-5">
|
|
<div>
|
|
<h5>FLO Address</h5>
|
|
<sm-copy value="${floID}"></sm-copy>
|
|
</div>
|
|
<div>
|
|
<h5>Private Key</h5>
|
|
<sm-copy value="${privKey}"></sm-copy>
|
|
</div>
|
|
</li>
|
|
<li class="grid gap-0-5">
|
|
<div>
|
|
<h5>Bitcoin Address</h5>
|
|
<sm-copy value="${btcAddr}"></sm-copy>
|
|
</div>
|
|
<div>
|
|
<h5>Bitcoin Private Key</h5>
|
|
<sm-copy value="${btcPrivKey}"></sm-copy>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
`
|
|
);
|
|
}
|
|
function retrieveBinanceAddr() {
|
|
function retrieve() {
|
|
try {
|
|
let inputPrivKey = getRef("retrieve_btc_addr_field").value.trim();
|
|
|
|
if (!inputPrivKey) {
|
|
notify("Please enter a private key", "error");
|
|
return;
|
|
}
|
|
|
|
let privKey, floID, ethPrivateKey, BSCAddress, btcPrivKey, btcAddr;
|
|
|
|
// Determine the format of the input private key and derive all addresses
|
|
if (
|
|
inputPrivKey.startsWith("R") ||
|
|
inputPrivKey.startsWith("L") ||
|
|
inputPrivKey.startsWith("K")
|
|
) {
|
|
// WIF format (FLO/BTC private key)
|
|
privKey = inputPrivKey;
|
|
floID = floCrypto.getFloID(privKey);
|
|
ethPrivateKey = coinjs.wif2privkey(privKey).privkey;
|
|
BSCAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey);
|
|
btcPrivKey = btcOperator.convert.wif(privKey);
|
|
btcAddr = btcOperator.bech32Address(btcPrivKey);
|
|
} else {
|
|
// Hex format (BSC/ETH private key)
|
|
ethPrivateKey = inputPrivKey;
|
|
BSCAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey);
|
|
// Convert hex private key to WIF format for FLO and BTC
|
|
privKey = coinjs.privkey2wif(ethPrivateKey);
|
|
floID = floCrypto.getFloID(privKey);
|
|
btcPrivKey = btcOperator.convert.wif(privKey);
|
|
btcAddr = btcOperator.bech32Address(btcPrivKey);
|
|
}
|
|
|
|
// Render all recovered addresses and private keys
|
|
renderElem(
|
|
getRef("recovered_addresses_wrapper"),
|
|
html`
|
|
<ul id="recovered_addresses" class="grid gap-1-5">
|
|
<li class="grid gap-0-5">
|
|
<div>
|
|
<h5>Recovered Binance Address</h5>
|
|
<sm-copy value="${BSCAddress}"></sm-copy>
|
|
</div>
|
|
<div>
|
|
<h5>Private Key</h5>
|
|
<sm-copy value="${ethPrivateKey}"></sm-copy>
|
|
</div>
|
|
</li>
|
|
<li class="grid gap-0-5">
|
|
<div>
|
|
<h5>Recovered FLO Address</h5>
|
|
<sm-copy value="${floID}"></sm-copy>
|
|
</div>
|
|
<div>
|
|
<h5>Private Key</h5>
|
|
<sm-copy value="${privKey}"></sm-copy>
|
|
</div>
|
|
</li>
|
|
<li class="grid gap-0-5">
|
|
<div>
|
|
<h5>Recovered Bitcoin Address</h5>
|
|
<sm-copy value="${btcAddr}"></sm-copy>
|
|
</div>
|
|
<div>
|
|
<h5>Bitcoin Private Key</h5>
|
|
<sm-copy value="${btcPrivKey}"></sm-copy>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
`
|
|
);
|
|
notify("Successfully recovered all addresses!", "success");
|
|
} catch (error) {
|
|
console.error("Error recovering addresses:", error);
|
|
notify(
|
|
"Failed to recover addresses. Please check your private key format.",
|
|
"error"
|
|
);
|
|
}
|
|
}
|
|
if (document.startViewTransition) {
|
|
document.startViewTransition(() => {
|
|
retrieve();
|
|
});
|
|
} else retrieve();
|
|
}
|
|
|
|
function testHistoricalAPIAndRenderToggle() {
|
|
// Get date from a week ago for testing historical data
|
|
const oneWeekAgo = Math.floor(Date.now() / 1000) - 7 * 24 * 60 * 60;
|
|
|
|
fetchHistoricalRate(oneWeekAgo)
|
|
.then((rates) => {
|
|
if (rates && (rates.usd > 0 || rates.inr > 0)) {
|
|
isHistoricApiAvailable = true;
|
|
renderValuationToggle();
|
|
} else {
|
|
isHistoricApiAvailable = false;
|
|
}
|
|
})
|
|
.catch(() => {
|
|
isHistoricApiAvailable = false;
|
|
});
|
|
}
|
|
|
|
function renderValuationToggle() {
|
|
if (isHistoricApiAvailable && currentCurrency !== "bsc") {
|
|
const container = document.querySelector(
|
|
".flex.flex-direction-column.gap-0-5.sticky.top-0"
|
|
);
|
|
if (container) {
|
|
const existingToggle = container.querySelector("sm-switch");
|
|
if (!existingToggle) {
|
|
const toggleDiv = document.createElement("div");
|
|
toggleDiv.className = "margin-left-auto";
|
|
toggleDiv.innerHTML = `
|
|
<sm-switch id="valuation_toggle" ${
|
|
showCurrentValue ? "checked" : ""
|
|
}>
|
|
<p slot="left" class="margin-right-0-5">
|
|
Show current value
|
|
</p>
|
|
</sm-switch>
|
|
`;
|
|
container.appendChild(toggleDiv);
|
|
|
|
// Add event handler to the new toggle
|
|
document
|
|
.getElementById("valuation_toggle")
|
|
.addEventListener("change", (e) => {
|
|
handleValuationTypeChange(e.target.checked);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|