const menuButton = document.querySelector(".menu-button"); const navigation = document.querySelector(".site-nav"); const form = document.querySelector("#faucet-form"); const address = document.querySelector("#wallet-address"); const message = document.querySelector("#form-message"); const copyAddress = document.querySelector("#copy-address"); const faucetAddress = document.querySelector("#faucet-address"); const requestButton = form.querySelector(".request-button"); let faucetState = { network: "testnet", symbol: "CLTC", claimAmount: "25.00000000", online: false, }; menuButton.addEventListener("click", () => { const open = navigation.classList.toggle("open"); menuButton.setAttribute("aria-expanded", String(open)); }); function displayAmount(value) { const numeric = Number(value); return Number.isFinite(numeric) ? numeric.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 8, }) : value; } function waitDescription(seconds) { const hours = Math.floor(seconds / 3600); const minutes = Math.ceil((seconds % 3600) / 60); return hours > 0 ? `${hours} hour${hours === 1 ? "" : "s"} and ${minutes} minute${minutes === 1 ? "" : "s"}` : `${minutes} minute${minutes === 1 ? "" : "s"}`; } function applyStatus(status) { const network = status.network || "testnet"; const symbol = status.symbol || "CLTC"; const claimAmount = status.claim_amount || "25.00000000"; const displayClaim = displayAmount(claimAmount); const networkTitle = network.charAt(0).toUpperCase() + network.slice(1); faucetState = { network, symbol, claimAmount, online: Boolean(status.online), }; document.querySelector("#brand-network").textContent = `${network.toUpperCase()} FAUCET`; document.querySelector("#network-coordinate").textContent = `${network.toUpperCase()} / ${displayClaim} ${symbol}`; document.querySelector("#eyebrow-network").textContent = network; document.querySelector("#intro-amount").textContent = `${displayClaim} ${symbol}`; document.querySelector("#intro-network").textContent = network; document.querySelector("#input-symbol").textContent = symbol; document.querySelector("#address-note").textContent = `The wallet must already be registered on ${network}.`; document.querySelector("#request-label").textContent = `Request ${displayClaim} ${symbol}`; document.querySelector("#rule-amount").textContent = displayClaim; document.querySelector("#rule-symbol").textContent = `${symbol} per request`; document.querySelector("#tip-label").textContent = `Send ${symbol} tips to:`; document.querySelector("#lower-network").textContent = networkTitle; document.querySelector("#footer-network").textContent = `${network.toUpperCase()} COINS HAVE NO MONETARY VALUE`; faucetAddress.textContent = status.faucet_address || `Faucet ${network} address is not configured`; document.querySelector("#faucet-balance").textContent = status.faucet_balance === null ? `-- ${symbol}` : `${displayAmount(status.faucet_balance)} ${symbol}`; document.querySelector("#total-claimed").textContent = `${displayAmount(status.total_claimed || "0")} ${symbol}`; document.querySelector("#total-addresses").textContent = Number(status.total_addresses || 0).toLocaleString(); const faucetStatus = document.querySelector("#faucet-status"); faucetStatus.lastChild.textContent = status.online ? " Online" : " Offline"; faucetStatus.classList.toggle("offline", !status.online); requestButton.disabled = !status.online; } async function refreshStatus() { try { const response = await fetch("api.php", { headers: { Accept: "application/json" }, cache: "no-store", }); const status = await response.json(); if (!response.ok || !status.success) { throw new Error(status.error || "Status request failed."); } applyStatus(status); } catch { applyStatus({ network: faucetState.network, symbol: faucetState.symbol, claim_amount: faucetState.claimAmount, online: false, faucet_address: "", faucet_balance: null, total_claimed: "0", total_addresses: 0, }); } } form.addEventListener("submit", async (event) => { event.preventDefault(); const value = address.value.trim(); const suffix = faucetState.network === "mainnet" ? "clc" : "cltc"; const escapedSuffix = suffix.replace(".", "\\."); const valid = new RegExp( `^[a-f0-9]{40}\\.${escapedSuffix}$`, "i", ).test(value); message.classList.add("visible"); message.classList.remove("success"); if (!valid) { message.textContent = `Enter a valid registered Contractless wallet address ending in .${suffix}.`; return; } requestButton.disabled = true; message.textContent = "Creating and broadcasting your faucet transaction..."; try { const response = await fetch("api.php", { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ address: value }), }); const result = await response.json(); if (!response.ok || !result.success) { const retry = result.retry_after_seconds ? ` Try again in ${waitDescription(result.retry_after_seconds)}.` : ""; throw new Error((result.error || "The faucet request failed.") + retry); } message.classList.add("success"); message.textContent = `${displayAmount(result.amount)} ${result.symbol} was broadcast. ` + `Transaction: ${result.txid}`; address.value = ""; await refreshStatus(); } catch (error) { message.textContent = error instanceof Error ? error.message : "The faucet request failed."; } finally { requestButton.disabled = !faucetState.online; } }); copyAddress.addEventListener("click", async () => { try { await navigator.clipboard.writeText(faucetAddress.textContent.trim()); copyAddress.textContent = "Copied"; } catch { copyAddress.textContent = "Copy failed"; } setTimeout(() => { copyAddress.textContent = "Copy address"; }, 1800); }); refreshStatus();