Skip to content

Commit

Permalink
docs: keyreg offline utility page https://nodekit.run/offline
Browse files Browse the repository at this point in the history
  • Loading branch information
Tasos Bitsios committed Feb 12, 2025
1 parent ce782cb commit 86a03f7
Show file tree
Hide file tree
Showing 2 changed files with 323 additions and 1 deletion.
9 changes: 8 additions & 1 deletion docs/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,18 @@ export const lang = "en";
</TabItem>
</Tabs>
</div>
<div class="flex flex-col gap-4">
<a
class="text-white/80 hover:text-[#BFBFF9]"
class="text-white/80 block hover:text-[#BFBFF9]"
href="https://developer.algorand.co/nodes/nodekit-quick-start"
>Guides &amp; Documentation <Icon name="external" />
</a>
<a
class="text-white/80 block hover:text-[#BFBFF9]"
href="/offline"
>Quick keyreg offline tool <Icon name="external" />
</a>
</div>
</div>
</div>
</div>
Expand Down
315 changes: 315 additions & 0 deletions docs/src/pages/offline.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
---
import { Code } from "@astrojs/starlight/components";
import { Icon } from "@astrojs/starlight/components";
import { Tabs, TabItem } from "@astrojs/starlight/components";
const base = import.meta.env.BASE_URL;
export const lang = "en";
---

<html lang="en" data-mode="dark" class="dark">
<head>
<title>NodeKit - Quick Keyreg Offline</title>
<meta
name="description"
content="Quickly sign a keyreg offline for your account(s)"
/>
<link rel="shortcut icon" href="/favicon.svg" type="image/svg+xml" />
<!-- Facebook Meta Tags -->
<meta property="og:url" content="https://nodekit.run/offline" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Quick Keyreg Offline Tool" />
<meta
property="og:description"
content="Quickly sign a keyreg offline for your account(s)"
/>
<meta property="og:image" content="/nodekit.png" />

<!-- Twitter Meta Tags -->
<meta name="twitter:card" content="summary_large_image" />
<meta property="twitter:domain" content="nodekit.run" />
<meta property="twitter:url" content="https://nodekit.run/offline" />
<meta name="twitter:title" content="Quick Keyreg Offline Tool" />
<meta
name="twitter:description"
content="Quickly sign a keyreg offline for your account(s)"
/>
<meta name="twitter:image" content="/nodekit.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style is:global>
* {
box-sizing: border-box;
}
html,
body {
max-height: 100svh;
width: 100%;
background: #001324;
scroll-behavior: smooth;
padding: 0;
margin: 0;
}
@media (prefers-reduced-motion: reduce) {
html,
body {
scroll-behavior: auto;
}
}
@keyframes scroll {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-50%);
}
}
.animate-scroll {
animation: scroll 120s linear infinite;
}
.expressive-code .copy button::before {
--ec-frm-inlBtnBrd: #bfbff9;
}
.expressive-code .copy button::after {
--ec-frm-inlBtnFg: #bfbff9;
}
</style>
<script>
interface OnlineStatus {
addr: string;
online: boolean;
label?: string;
balance: number;
}
const shorten = (str: String, length = 8) => {
return str.slice(0, length) + ".." + str.slice(-length);
};
const appendResults = (addresses: OnlineStatus[]) => {
const str = addresses
.map(({ addr, label, online, balance }) => {
const destLink = `https://lora.algokit.io/mainnet/transaction-wizard?type[0]=keyreg&sender[0]=${addr}`;
return `
<div class="my-4 border-2 p-4 border-white/20 bg-blue-500/10 rounded-lg max-w-[calc(min(98vw,440px))] flex flex-col gap-3">
<div class="flex text-sm justify-between font-bold text-white">
<div class="flex">Address</div>
<div class="max-w-full flex flex-nowrap">
<div class="overflow-hidden whitespace-nowrap break-all">${addr.slice(0, 8)}</div>
<div><span class="mr-[1px]">..</span>${addr.slice(-6)}</div>
</div>
</div>
<div class="flex flex-col gap-1">
<div class="flex text-sm justify-between"><span>Balance</span><span>${balance !== undefined ? `${(balance / 1e6).toLocaleString()} ALGO` : "-"}</span></div>
<div class="flex text-sm justify-between"><span>Participating</span><span class="${online ? "text-green-400" : "text-red-400"}">${online ? "Yes" : "No"}</span></div>
</div>
<div class="flex justify-between items-center">
${label ? `<div class="flex text-xs px-2 py-1 bg-[#a9a9f6]/20 rounded-md justify-between -ml-1 -mb-4">NFD ${label}</div>` : `<div></div>`}
<a href="${destLink}" target="_blank" class="self-end"><button class="rounded-lg px-4 py-2 text-sm bg-[#a9a9f6]">Register Offline</button></a>
</div>
</div>
`;
})
.join("");
document.getElementById("results")!.innerHTML = str;
};
const clearResults = () => {
document.getElementById("results")!.innerHTML = "";
};
const hideResults = () => {
const elem = document.getElementById("results-container")!;
if (elem?.style.display !== "none") {
elem.style.display = "none";
}
};
const showResults = () => {
setStatus("Results");
const elem = document.getElementById("results-container")!;
if (elem?.style.display !== "block") {
elem.style.display = "block";
}
};
// @ts-ignore
window.changeQuery = async () => {
const elem: HTMLInputElement = document.getElementById(
"input-query"
) as HTMLInputElement;
const addr = elem!.value.trim();
if (addr.length === 58 || addr.endsWith(".algo")) {
elem.disabled = true;
clearResults();
showResults();
setStatus("Loading");
try {
setStatus("");
if (addr.length === 58) {
const data = await getOnlineStatus(addr);
appendResults([{ addr, ...data }]);
} else {
const addrsWithLabel = await lookupNFD(addr);
const data = await Promise.all(
Object.keys(addrsWithLabel).map((addr) => getOnlineStatus(addr))
);
const results = Object.entries(addrsWithLabel).map(
([addr, label], i) => ({
addr,
label,
balance: data[i].balance,
online: data[i].online,
})
);
appendResults(results);
}
} catch (e) {
setStatus('<div class="text-red-400">Something went wrong</div>');
document.getElementById("results")!.innerHTML = `
<div class="flex flex-col gap-2">
<div>Error: ${(e as Error).message}</div>
</div>
`;
} finally {
elem.disabled = false;
elem.focus();
}
} else {
hideResults();
}
};
const sleep = async (ms: number) =>
new Promise((resolve) => setTimeout(resolve, ms));
const setStatus = (status: string) => {
document.getElementById("status")!.innerHTML = status;
};
async function lookupNFD(name: string) {
const resp = await fetch(
`https://api.nf.domains/nfd/${name}?view=tiny`
);
if (resp.status === 404) throw new Error("NFD Not found");
const data = await resp.json();
const { caAlgo = [], depositAccount, unverifiedCaAlgo = [] } = data;
const res: Record<string, string> = {};
if (depositAccount) res[depositAccount] = "Deposit";
for (const verified of caAlgo) {
res[verified] = res[verified] ?? "Verified";
}
for (const unverified of unverifiedCaAlgo) {
res[unverified] = res[unverified] ?? "Unverified";
}
console.log(data, res);
return res;
}
async function getOnlineStatus(
addr: string
): Promise<{ online: boolean; balance: number }> {
const resp = await fetch(
`https://mainnet-api.algonode.cloud/v2/accounts/${addr}`
);
const data = await resp.json();
if (resp.status >= 400)
throw new Error(data.message ?? `HTTP Error ${resp.status}`);
// this properly captures suspended & expired as well
const online =
data.status === "Online" &&
data.participation["vote-last-valid"] &&
data.participation["vote-last-valid"] >= data.round;
return { online, balance: data.amount };
}
</script>
</head>
<body>
<main class="flex justify-center text-white/80">
<div
class="relative w-full xl:max-w-7xl flex flex-col items-center lg:px-4"
>
<div class="absolute w-full flex justify-between items-center">
<a href="/"
><img
src="/nodekit.png"
alt="Algo Nodekit logo"
width="250"
height="auto"
/></a
>
<a
class="mr-10"
target="_blank"
href="https://github.com/algorandfoundation/nodekit"
>
<svg
class="w-8 h-8 fill-white hover:fill-[#BFBFF9]"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 496 512"
>
{
/* <!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--> */
}
<path
d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"
></path>
</svg>
</a>
</div>
<div
class="w-full h-svh mx-4 flex flex-col"
style={{ justifyContent: "safe center" }}
>
<div
class="w-full mt-14 flex gap-10 flex-col items-center justify-center px-6 pb-10"
>
<div class="font-bold text-xl text-[#a9a9f6]">
Quick <span class="text-white/90">keyreg offline</span> tool
</div>
<input
type="text"
id="input-query"
class="bg-gray-50 disabled:bg-gray-500 border border-gray-300 disabled:border-gray-700 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-[calc(min(98vw,440px))] max-w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Enter your account address or NFD"
required
autofocus
oninput="changeQuery()"
/>
<div
id="results-container"
class="w-[calc(min(98vw,440px))] max-w-full"
>
<div
id="status"
class="text-white/70 mt-2 mb-4 flex flex-col gap-1"
>
<div class="mb-2 text-white">
Need to register offline in a jiffy? Easy as 1-2:
</div>
<div class="text-white">
1. Find your account by address or NFD.
</div>
<div class="text-white">2. Sign offline keyreg via Lora.</div>
<div class="mt-8">
<i>This is a web based tool. Node access is not required.</i>
</div>
<div class="mt-8">
<i
>Key registration changes require 320 rounds to take effect.
When possible, leave your node running for 15 minutes after
registering offline.</i
>
</div>
</div>
<div id="results" class=""></div>
</div>
</div>
</div>
</div>
<script>
// support passing in query string as url fragment/hash
// e.g. /offline#ADDRESS
if (window.location.hash) {
setTimeout(() => {
const elem = document.getElementById(
"input-query"
) as HTMLInputElement;
elem.value = window.location.hash.slice(1);
// @ts-ignore
changeQuery();
}, 13);
}
</script>
</main>
</body>
</html>

0 comments on commit 86a03f7

Please sign in to comment.