184 lines
6.2 KiB
JavaScript
184 lines
6.2 KiB
JavaScript
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 or vanity address 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 canonicalAddress = new RegExp(
|
|
`^[a-f0-9]{40}\\.${escapedSuffix}$`,
|
|
"i",
|
|
);
|
|
const vanityAddress = new RegExp(
|
|
`^[a-z]{1,20}\\.${escapedSuffix}$`,
|
|
"i",
|
|
);
|
|
const valid = canonicalAddress.test(value) || vanityAddress.test(value);
|
|
|
|
message.classList.add("visible");
|
|
message.classList.remove("success");
|
|
if (!valid) {
|
|
message.textContent =
|
|
`Enter a valid registered Contractless wallet or vanity 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();
|