solanawallet/index.html
2024-07-06 15:00:10 +05:30

1657 lines
68 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>Solana Wallet</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>
</div>
</sm-popup>
<!-- // Main file to control all ui files -->
<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">Solana Wallet</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>
<theme-toggle></theme-toggle>
</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>
<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="https://unpkg.com/@solana/web3.js@latest/lib/index.iife.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bs58@5.0.0/index.min.js"></script>
<script
src="scripts/btcwallet_scripts_lib.js"
type="text/javascript"
></script>
<script src="scripts/btcOperator.js" type="text/javascript"></script>
<script src="scripts/floCrypto.js" type="text/javascript"></script>
<script src="scripts/floSolana.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="https://cdn.ethers.io/lib/ethers-5.6.umd.min.js"
type="text/javascript"
></script>
<script src="scripts/ethOperator.js" type="text/javascript"></script>
<script>
const uiGlobals = {};
const { html, svg, render: renderElem } = uhtml;
uiGlobals.connectionErrorNotification = [];
//Checks for internet connection status
if (!navigator.onLine)
uiGlobals.connectionErrorNotification.push(
notify(
"There seems to be a problem connecting to the internet, Please check you internet connection.",
"error"
)
);
window.addEventListener("offline", () => {
uiGlobals.connectionErrorNotification.push(
notify(
"There seems to be a problem connecting to the internet, Please check you internet connection.",
"error"
)
);
});
window.addEventListener("online", () => {
uiGlobals.connectionErrorNotification.forEach((notification) => {
getRef("notification_drawer").remove(notification);
});
notify("We are back online.", "success");
});
// Use instead of document.getElementById
function getRef(elementId) {
return document.getElementById(elementId);
}
let zIndex = 50;
// function required for popups or modals to appear
function openPopup(popupId, pinned) {
if (popupStack.peek() === undefined) {
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
closePopup();
}
});
}
zIndex++;
getRef(popupId).setAttribute("style", `z-index: ${zIndex}`);
return getRef(popupId).show({ pinned });
}
// hides the popup or modal
function closePopup(options = {}) {
if (popupStack.peek() === undefined) return;
popupStack.peek().popup.hide(options);
}
document.addEventListener("popupopened", async (e) => {
switch (e.target.id) {
}
});
document.addEventListener("popupclosed", (e) => {
zIndex--;
switch (e.target.id) {
}
});
//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 = {
sol: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="#000000">
<path d="M16 0c8.837 0 16 7.163 16 16s-7.163 16-16 16S0 24.837 0 16 7.163 0 16 0zm8.706 19.517H10.34a.59.59 0 00-.415.17l-2.838 2.815a.291.291 0 00.207.498H21.66a.59.59 0 00.415-.17l2.838-2.816a.291.291 0 00-.207-.497zm-3.046-5.292H7.294l-.068.007a.291.291 0 00-.14.49l2.84 2.816.07.06c.1.07.22.11.344.11h14.366l.068-.007a.291.291 0 00.14-.49l-2.84-2.816-.07-.06a.59.59 0 00-.344-.11zM24.706 9H10.34a.59.59 0 00-.415.17l-2.838 2.816a.291.291 0 00.207.497H21.66a.59.59 0 00.415-.17l2.838-2.815A.291.291 0 0024.706 9z"/>
</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-sol-address]",
customValidation: (value) => {
if (!value)
return {
isValid: false,
errorText:
"Please enter a Solana address",
};
return {
isValid: (() => {
try {
new solanaWeb3.PublicKey(value);
return true;
} catch {
return false;
}
})(),
errorText: `Invalid Solana address.`,
};
},
},
{
selector: "[data-private-key]",
customValidation: (value) => {
if (!value)
return {
isValid: false,
errorText:
"Please enter a Solana Seed OR Bitcoin Private Key OR FLO Private Key",
};
return {
isValid: (() => {
try {
const secret =
value.length < 64
? floSolana.wif2SolanaSecret(value)
: floSolana.solanaSeed2SolanaSecret(value);
const secretKey =
floSolana.solanaSecret2UsableInCode(secret);
const keypair = solanaWeb3.Keypair.fromSecretKey(
Uint8Array.from(
secretKey.toString().split(",").map(Number)
)
);
return true;
} catch (err) {
return false;
}
})(),
errorText: `Invalid Solana Seed OR Bitcoin Private Key OR FLO Private Key`,
};
},
},
{
selector: "#wallet_address_input",
customValidation: (value) => {
if (!value)
return {
isValid: false,
errorText:
"Please enter a Solana Address OR Bitcoin Private Key OR FLO Private Key",
};
return {
isValid: (() => {
try {
const solana = new solanaWeb3.PublicKey(value);
return true;
} catch (err) {
try {
const solonaAdress = floSolana.validationOfWif(value);
new solanaWeb3.PublicKey(solonaAdress);
return true;
} catch (error) {
return false;
}
}
})(),
errorText:
"Invalid Solana Address OR Bitcoin Private Key OR FLO Private Key",
};
},
},
],
};
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();
}
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");
});
document.addEventListener("pointerdown", (e) => {
if (
e.target.closest(
"button:not(:disabled), .interactive:not(:disabled)"
)
) {
createRipple(e, e.target.closest("button, .interactive"));
}
});
compactIDB
.initDB("solanaWallet", {
solanaContacts: {},
})
.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 !== "0x1") {
renderError("Please switch MetaMask to Ethereum Mainnet");
} else {
router.routeTo(location.hash);
}
});
window.ethereum
.request({
method: "eth_chainId",
})
.then((chainId) => {
window.currentChainId = chainId;
if (chainId !== "0x1") {
renderError("Please switch MetaMask to Ethereum Mainnet");
} else {
router.routeTo(location.hash);
}
});
}
// }).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("sol_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", renderHome);
function handleInvalidSearch() {
if (document.startViewTransition)
document.startViewTransition(() => {
getRef("sol_balance_wrapper").classList.add("hidden");
});
else {
getRef("sol_balance_wrapper").classList.add("hidden");
}
}
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 Solana Wallet SOL balance</h2>
<sm-form>
<div id="input_wrapper">
<sm-input
id="wallet_address_input"
class="password-field flex-1"
placeholder="Solana Address OR Bitcoin Private Key OR FLO Private Key"
type="password"
animate
required
>
<svg
class="icon"
slot="icon"
xmlns="http://www.w3.org/2000/svg"
enable-background="new 0 0 24 24"
height="24px"
viewBox="0 0 24 24"
width="24px"
fill="#000000"
>
<g><rect fill="none" height="24" width="24"></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="sol_balance_wrapper" class="grid gap-2 hidden"></div>
</section>
`
);
if (
window.ethereum &&
!(window.currentChainId && window.currentChainId === "0x1")
) {
renderError("Please switch MetaMask to Ethereum 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-27.4 42.1 58.5 16.1 16.7-56.8zm61.4-99.1 18.3-8.7-58.4-26.3zM20.8 99 38.7 107.7l56.8-26.3zM197.7 206l-77.1-2.6 3.1 27.5-58.8 16.1 15.8 56.8 48.6-35 11.1 9.2 2.5 7.9h29.6l3.1-8.1 10.2-9.2 48.6 35 16.7-56.8-58.4-16.1zm-75.4-55.7 49.7-.5-51.2-23.2zM123.8 206l26.7-32.1-54.5-1.3zm71.1-32.1 26.8 32.1 27.8-33.4zm12.7-1.1-17.1-31.7 36.7-1.5zm-52.4-33.2-16.6 32.2-20-33.6zm101.7-57.7-12.2 24.5 41.5 2.6 4.7-39.4zM44.7 107.7l4.4 39.4 41.2-2.6-12.2-24.5zM101.2 170.5l27.2 33.4 27.1-32.1-17.6-32zm117.2 0-36.7-1.5 27.2 33.6zm-117.5 79.5 32.6-9.7-4.7 39.4zm85.7 29.7h-33l3.1-39.2h26.6zm21.5-40.3 32.5 9.7-27.8-48.8z"
class="st6"
/>
</svg>
<h2>${title}</h2>
<p>${description}</p>
</section>
`
);
}
function renderSearchedAddressList() {
compactIDB
.readAllData("solanaContacts")
.then((solanaContacts) => {
if (!getRef("searched_addresses_list")) return;
if (Object.keys(solanaContacts).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 keys in solanaContacts) {
const walletAddress = solanaContacts[keys].walletAddress;
const isFloAddr = solanaContacts[keys].isfloaddr;
console.log("isfloaddr", isFloAddr);
renderedContacts.push(html`<li
class="contact"
.dataset=${{ address: walletAddress }}
>
<sm-copy value="${walletAddress}"></sm-copy>
${!isFloAddr
? null
: html`<sm-copy value="${isFloAddr}"></sm-copy>`}
<div class="flex align-center space-between gap-0-5">
<button
class="button button--small"
onclick=${() => deleteContact(walletAddress)}
>
Delete
</button>
<button
class="button button--colored button--small"
onclick=${() => checkBalance(walletAddress)}
>
Check balance
</button>
</div>
</li>`);
}
renderElem(
getRef("searched_addresses_list"),
html`${renderedContacts}`
);
})
.catch((error) => {
console.error(error);
});
}
const getWalletBalance = async (walletAddressString) => {
try {
const connection = new solanaWeb3.Connection(
"https://solana-mainnet.core.chainstack.com/3eb691b8be8f475e3a74f765abfa0b03",
{
wsEndpoint:
"wss://solana-mainnet.core.chainstack.com/3eb691b8be8f475e3a74f765abfa0b03",
}
);
console.log("Connection object is:", connection);
// Create a PublicKey object from the wallet address string
const walletPublicKey = new solanaWeb3.PublicKey(walletAddressString);
const walletBalance = await connection.getBalance(walletPublicKey);
console.log(
`Wallet balance: ${walletBalance / solanaWeb3.LAMPORTS_PER_SOL} SOL`
);
return walletBalance;
} catch (err) {
notify(err, "error");
}
};
const checkBalance = async (walletAddress) => {
// const privateKey = document.getElementById("private_key_input").value;
if (!walletAddress) {
walletAddress = document.getElementById("wallet_address_input").value;
}
const convert2Address = walletAddress.startsWith("R")
? floCrypto.getAddress(walletAddress)
: floCrypto.getAddress(walletAddress, true);
const isfloaddr = walletAddress.length <= 44 ? "" : convert2Address;
walletAddress =
walletAddress.length <= 44
? walletAddress
: floSolana.wif2SolanaAddress(walletAddress);
const Keys = !isfloaddr
? { walletAddress }
: { walletAddress, isfloaddr };
console.log("wallet", Keys);
buttonLoader("check_balance_button", true);
try {
const walletBalance = await getWalletBalance(walletAddress);
const solBalance = walletBalance / solanaWeb3.LAMPORTS_PER_SOL;
// Interact with IndexedDB
const contactExists = await compactIDB.readData(
"solanaContacts",
walletAddress
);
if (!contactExists) {
await compactIDB.addData("solanaContacts", Keys, walletAddress);
await renderSearchedAddressList();
}
// Render the balances
// document.getElementById("sol_balance_wrapper").innerHTML =
renderElem(
getRef("sol_balance_wrapper"),
html`
<div class="grid">
<div class="label">Solana Wallet Address</div>
<sm-copy id="sol_address">${walletAddress}</sm-copy>
</div>
${walletBalance === solBalance
? html`
<div class="grid gap-1">
<h4>Balance</h4>
<ul
id="sol_address_balance"
class="flex flex-direction-column gap-0-5"
>
<li class="flex align-center space-between">
<p>SOL</p>
<b id="sol_balance">${solBalance} SOL</b>
</li>
</ul>
</div>
`
: ""}
`
);
getRef("sol_balance_wrapper").classList.remove("hidden");
getRef("sol_balance_wrapper").animate(
[
{
transform: "translateY(-1rem)",
opacity: 0,
},
{
transform: "none",
opacity: 1,
},
],
{
easing: "ease",
duration: 300,
fill: "forwards",
}
);
// document
// .getElementById("sol_balance_wrapper")
// .classList.remove("hidden");
} catch (error) {
notify(error, "error");
} finally {
buttonLoader("check_balance_button", false);
}
};
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("solanaContacts", 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 Solana 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 Solana Seed OR Bitcoin Private Key OR FLO 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 Solana address"
data-sol-address
animate
required
></sm-input>
<div class="flex flex-direction-column gap-0-5">
<sm-input
class="receiver-amount amount-shown flex-1"
placeholder="SOL Amount"
type="number"
step="0.000001"
min="0.000001"
error-text="Amount should be grater than 0.000001 SOL"
animate
required
>
<div class="asset-symbol flex" slot="icon">
<svg
class="icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 32 32"
fill="#000000"
>
<path
d="M16 0c8.837 0 16 7.163 16 16s-7.163 16-16 16S0 24.837 0 16 7.163 0 16 0zm8.706 19.517H10.34a.59.59 0 00-.415.17l-2.838 2.815a.291.291 0 00.207.498H21.66a.59.59 0 00.415-.17l2.838-2.816a.291.291 0 00-.207-.497zm-3.046-5.292H7.294l-.068.007a.291.291 0 00-.14.49l2.84 2.816.07.06c.1.07.22.11.344.11h14.366l.068-.007a.291.291 0 00.14-.49l-2.84-2.816-.07-.06a.59.59 0 00-.344-.11zM24.706 9H10.34a.59.59 0 00-.415.17l-2.838 2.816a.291.291 0 00.207.497H21.66a.59.59 0 00.415-.17l2.838-2.815A.291.291 0 0024.706 9z"
/>
</svg>
</div>
</sm-input>
</div>
</div>
</div>
<div class="multi-state-button">
<button
id="send_tx_button"
class="button button--primary"
type="submit"
disabled
onclick="sendTx()"
>
Send SOL
</button>
</div>
</fieldset>
</sm-form>
`
);
if (
window.ethereum &&
!(window.currentChainId && window.currentChainId === "0x1")
) {
renderError("Please switch MetaMask to Ethereum Mainnet");
}
});
function togglePrivateKeyVisibility(input) {
const target = input.closest("sm-input");
target.type = target.type === "password" ? "text" : "password";
target.focusIn();
}
function checkSenderBalance() {
let address;
const secret = getRef("private_key_input").value;
const pKey =
secret.length < 64
? floSolana.wif2SolanaSecret(secret)
: floSolana.solanaSeed2SolanaSecret(secret);
const privateKey = floSolana.solanaSecret2UsableInCode(pKey);
if (!privateKey)
return notify(`Please enter sender's private key to check balance`);
if (privateKey) {
const privateKeyArray = Uint8Array.from(
privateKey.toString().split(",").map(Number)
);
console.log(privateKeyArray);
const keypair = solanaWeb3.Keypair.fromSecretKey(privateKeyArray);
address = keypair.publicKey.toString();
console.log("solana address", address);
}
getRef("sender_balance_container").classList.remove("hidden");
renderElem(
getRef("sender_balance_container"),
html` Loading balance...<sm-spinner></sm-spinner> `
);
const promises = [getWalletBalance(address)];
const selectedAsset = "sol";
if (selectedAsset !== "sol")
promises.push(ethOperator.getTokenBalance(address, selectedAsset));
Promise.all(promises)
.then(([solBalance, 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">${solBalance} SOL</b>
${selectedAsset !== "sol"
? html`|
<b class="amount-shown"
>${tokenBalance} ${selectedAsset.toUpperCase()}</b
>`
: ""}
</p>
</div>
`
);
})
.catch((err) => {
notify(err, "error");
});
}
function handleSenderInput(e) {
getRef("check_balance_button").disabled = !e.target.isValid;
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()}`
);
}
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 = "sol";
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;
const secret = getRef("private_key_input").value.trim();
const pKey =
secret.length < 64
? floSolana.wif2SolanaSecret(secret)
: floSolana.solanaSeed2SolanaSecret(secret);
let privateKey = floSolana.solanaSecret2UsableInCode(pKey);
console.log("private key", privateKey);
const senderPrivateKeyArray = privateKey
const senderKeypair = solanaWeb3.Keypair.fromSecretKey(
senderPrivateKeyArray
);
if (/^[0-9a-fA-F]{64}$/.test(privateKey)) {
privateKey = coinjs.privkey2wif(privateKey);
}
privateKey = coinjs.wif2privkey(privateKey).privkey;
switch (asset) {
case "sol": {
const receiverPublicKey = new solanaWeb3.PublicKey(receiver);
console.log("reciver", receiverPublicKey);
const transaction = new solanaWeb3.Transaction().add(
solanaWeb3.SystemProgram.transfer({
fromPubkey: senderKeypair.publicKey,
toPubkey: receiverPublicKey,
lamports: amount * solanaWeb3.LAMPORTS_PER_SOL, // Convert SOL to lamports
})
);
const signature = await solanaWeb3.sendAndConfirmTransaction(
getWalletBalance.connection,
transaction,
[senderKeypair]
);
showTransactionResult("pending", { txHash: signature });
await signature.wait();
showTransactionResult("confirmed", { txHash: signature });
break;
}
case "usdc":
case "usdt": {
const tx = await ethOperator.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);
notify(e.message, "error");
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://etherscan.io/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://etherscan.io/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 Solana Address OR Bitcoin Private Key OR FLO Private Key?
Create one
</h2>
<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 new address
</button>
</div>
<div id="created_address_wrapper" class="grid gap-1"></div>
`
);
});
function bnObjectToUint8(bnObject) {
const bn = bnObject._bn; // Extract the BN instance from the object
const words = bn.words; // Get the words array from the BN instance
// Convert each word to its corresponding bytes
const byteArray = [];
for (let i = 0; i < words.length; i++) {
const word = words[i];
byteArray.push((word >> 24) & 0xff); // Extract first byte
byteArray.push((word >> 16) & 0xff); // Extract second byte
byteArray.push((word >> 8) & 0xff); // Extract third byte
byteArray.push(word & 0xff); // Extract fourth byte
}
// Create Uint8Array from the byte array
const uint8Array = new Uint8Array(byteArray);
return uint8Array;
}
function bnObjectToUint8(bnObject) {
const bn = bnObject._bn; // Extract the BN instance from the object
const words = bn.words; // Get the words array from the BN instance
// Convert each word to its corresponding bytes
const byteArray = [];
for (let i = 0; i < words.length; i++) {
const word = words[i];
byteArray.push((word >> 24) & 0xff); // Extract first byte
byteArray.push((word >> 16) & 0xff); // Extract second byte
byteArray.push((word >> 8) & 0xff); // Extract third byte
byteArray.push(word & 0xff); // Extract fourth byte
}
// Create Uint8Array from the byte array
const uint8Array = new Uint8Array(byteArray);
return uint8Array;
}
function generateNewID() {
const solanaKeyPair = floSolana.generateSolanaKeyPair();
renderElem(
getRef("created_address_wrapper"),
html`
<ul id="generated_addresses" class="grid gap-1-5">
<li class="grid gap-0-5">
<div>
<h5>Solana Seed</h5>
<sm-copy value="${solanaKeyPair.seed}"></sm-copy>
</div>
<div>
<h5>Solana Address</h5>
<sm-copy value="${solanaKeyPair.publicKey}"></sm-copy>
</div>
</li>
<li class="grid gap-0-5">
<div>
<h5>Bitcoin Private Key</h5>
<sm-copy value="${solanaKeyPair.bitcoinWif}"></sm-copy>
</div>
<div>
<h5>Bitcoin Address</h5>
<sm-copy value="${solanaKeyPair.bitcoinAddress}"></sm-copy>
</div>
</li>
<li class="grid gap-0-5">
<div>
<h5>Flo Private Key</h5>
<sm-copy value="${solanaKeyPair.floWif}"></sm-copy>
</div>
<div>
<h5>Flo Address</h5>
<sm-copy value="${solanaKeyPair.floAddress}"></sm-copy>
</div>
</li>
</ul>
`
);
}
</script>
</body>
</html>