bscwallet/index.html

3456 lines
138 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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.min.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"
rel="stylesheet"
/>
</head>
<body class="hidden">
<sm-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 Binance Address?</h4>
<p>
If you have your BSC/BTC/FLO Private Key, enter it here and recover
your Binance Address.
</p>
</div>
<sm-form>
<div id="recovered_btc_addr_wrapper" class="hidden">
<h5>Recovered Binance Address</h5>
<sm-copy id="recovered_btc_addr"></sm-copy>
</div>
<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>
</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");
});
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";
if (page !== "send") {
taprootScriptTxDetails = {};
}
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) => {
console.log(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 } = 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);
}
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();
}
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;
nextButton.onclick = () => this.goToPage(this.currentPage + 1);
// Page info
const pageInfo = document.createElement("span");
pageInfo.textContent = `Page ${this.currentPage} of ${this.totalPages}`;
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) {
this.elementsToRender = newElements;
this.totalPages = Math.ceil(newElements.length / this.pageSize);
this.currentPage = 1;
this.init();
}
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;
// Add render object with transaction rendering functions
const render = {
async transactions(address) {
try {
if (!address || !bscOperator.isValidAddress(address)) {
return;
}
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)]).then(
async ([response]) => {
if (!response || response.error) {
getRef("transactions_list").textContent =
"Error fetching transactions";
return;
}
const data = response;
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;
});
// Filter
const filter = getRef("filter_selector").value || "all";
let filteredTransactions = transactions;
if (filter === "sent") {
filteredTransactions = transactions.filter(
(tx) => tx.type === "out"
);
} else if (filter === "received") {
filteredTransactions = transactions.filter(
(tx) => tx.type === "in"
);
}
transactionsLazyLoader = new LazyLoader(
"#transactions_list",
filteredTransactions,
render.transactionCard,
{
pageSize: 10,
}
);
if (transactionsLazyLoader) {
transactionsLazyLoader.update(filteredTransactions);
} else {
transactionsLazyLoader = new LazyLoader(
"#transactions_list",
filteredTransactions,
render.transactionCard,
{
pageSize: 10,
}
);
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);
});
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").textContent =
"No transactions found";
}
}
);
} catch (error) {
console.error("[DEBUG] Error fetching transactions:", error);
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 > 6
? "Confirmed"
: `${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">
<h2>Check Binance, 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="FLO/BTC private key or BSC address"
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 class="multi-state-button">
<button
id="check_balance_button"
class="button button--primary h-100 w-100"
type="submit"
onclick=${() => checkBalance()}
disabled
>
Check balance
</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 floAddress in contacts) {
const { BSCAddress } = contacts[floAddress];
renderedContacts.push(html` <li
class="contact"
.dataset=${{ floAddress, BSCAddress }}
>
${floAddress === BSCAddress
? html``
: html`
<sm-chips
onchange=${(e) =>
(e.target
.closest(".contact")
.querySelector("sm-copy").value = e.target.value)}
>
<sm-chip value=${floAddress} selected
>${floAddress.startsWith("F")
? "FLO"
: "BTC"}</sm-chip
>
<sm-chip value=${BSCAddress}>BSC</sm-chip>
</sm-chips>
`}
<sm-copy value="${floAddress}"></sm-copy>
<div class="flex align-center space-between gap-0-5">
<button
class="button button--small"
onclick=${() => deleteContact(floAddress)}
>
Delete
</button>
<button
class="button button--colored button--small"
onclick=${() => checkBalance(BSCAddress, floAddress)}
>
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;">
<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">
<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"></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"
? "Transaction Confirmed"
: "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_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 (BSCAddress && getRef("check_balance_input")) {
getRef("check_balance_input").value = BSCAddress;
}
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 {
if (/^[0-9a-fA-F]{64}$/.test(keyToConvert)) {
keyToConvert = coinjs.privkey2wif(keyToConvert);
}
const ethPrivateKey = coinjs.wif2privkey(keyToConvert).privkey;
BSCAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey);
floAddress = keyToConvert.startsWith("R")
? floCrypto.getFloID(keyToConvert)
: btcOperator.bech32Address(keyToConvert);
}
}
if (!BSCAddress) return;
// Store the address for navigation context
window.lastViewedAddress = BSCAddress;
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]) => {
compactIDB
.readData("contacts", floAddress || BSCAddress)
.then((result) => {
if (result) 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);
});
}
function handleInvalidSearch() {
if (document.startViewTransition) {
document.startViewTransition(() => {
getRef("bsc_balance_wrapper").classList.add("hidden");
getRef("address_transactions").classList.add("hidden");
getRef("check_balance_input").value = "";
});
} else {
getRef("bsc_balance_wrapper").classList.add("hidden");
getRef("address_transactions").classList.add("hidden");
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) {
console.log(input)
const target = input.closest("sm-input");
// const checkBalanceButton = document.querySelector("#check_balance_button");
console.log(target)
target.type = target.type === "password" ? "text" : "password";
// target.focus();
// checkBalanceButton.disabled = false;
}
// const toggleButton = document.querySelector('#toggleButton');
// const privateKeyInput = document.querySelector('#private_key_input');
// toggleButton.addEventListener('click', () => {
// togglePrivateKeyVisibility(privateKeyInput);
// });
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 promises = [bscOperator.getBalance(address)];
const selectedAsset = getRef("asset_selector").value;
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) {
console.log(e.target.isValid)
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",
});
buttonLoader("send_tx_button", true);
if (!confirmation) return;
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();
// getRef("sender_balance_container").classList.add("hidden");
} catch (e) {
console.error(e.message);
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");
// }
// *************************************************************************
// ***************************************************************************
// function showTransactionResult(status, { txHash, description = "Insufficient BSC balance" })
// { 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>"Insufficient BSC balance"</p> </div> ` );
// break;
// } openPopup("transaction_result_popup"); }
// showTransactionResult("failed", { txHash: "your-tx-hash-here", description: "Insufficient BSC balance" });
function showTransactionResult(status, { txHash, usdtBalance, bscBalance }) {
let description = "Insufficient BSC balance";
if (usdtBalance <= 0) {
description = "Insufficient USDT balance";
} else if (bscBalance <= 0) {
description = "Insufficient BSC balance";
}
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() {
let bscPrivateKey = getRef("retrieve_btc_addr_field").value.trim();
getRef("recovered_btc_addr_wrapper").classList.remove("hidden");
getRef("recovered_btc_addr").value =
bscPrivateKey.startsWith("R") ||
bscPrivateKey.startsWith("L") ||
bscPrivateKey.startsWith("K")
? floEthereum.ethAddressFromPrivateKey(
coinjs.wif2privkey(bscPrivateKey).privkey
)
: floEthereum.ethAddressFromPrivateKey(bscPrivateKey);
}
if (document.startViewTransition) {
document.startViewTransition(() => {
retrieve();
});
} else retrieve();
}
function testHistoricalAPIAndRenderToggle() {
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);
document
.getElementById("valuation_toggle")
.addEventListener("change", (e) => {
handleValuationTypeChange(e.target.checked);
});
}
}
}
}
</script>
</body>
</html>
<!-- function showTransactionResult(status, { txHash, description = "Insufficient BSC balance" }) { 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>"Insufficient BSC balance"</p> </div> ` ); break; } openPopup("transaction_result_popup"); } showTransactionResult("failed", { txHash: "your-tx-hash-here", description: "Insufficient BSC balance" }); -->