dappbundle/bscwallet/index.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>