Contractless-GUI-Wallet/frontend/js/create_nft_form.js

249 lines
8.2 KiB
JavaScript
Raw Normal View History

2026-07-01 16:01:30 +00:00
const CREATE_NFT_FEE_ATOMIC = 50000000n;
let createNftBalancesRefreshPromise = null;
function setCreateNftMessage(message, type = "") {
setScopedTransactionMessage("Create NFT", createNftMessage, message, type);
}
function createNftSeriesValue() {
return Number(createNftSeriesSelect?.value || 0) === 1 ? 1 : 0;
}
function normalizeCreateNftCountInput() {
const isCollection = createNftSeriesValue() === 1;
if (createNftCountInput) {
createNftCountInput.disabled = !isCollection;
createNftCountInput.min = isCollection ? "2" : "1";
if (!isCollection) {
createNftCountInput.value = "1";
} else if (Number(createNftCountInput.value || 0) < 2) {
createNftCountInput.value = "2";
}
}
}
function updateCreateNftDerivedFields({ clampCount = false } = {}) {
const isCollection = createNftSeriesValue() === 1;
if (clampCount) {
normalizeCreateNftCountInput();
} else if (createNftCountInput) {
createNftCountInput.disabled = !isCollection;
createNftCountInput.min = isCollection ? "2" : "1";
}
const rawCount = String(createNftCountInput?.value || "").trim();
const parsedCount = Number.parseInt(rawCount, 10);
const countLabel = Number.isInteger(parsedCount) && parsedCount > 0 ? String(parsedCount) : "--";
if (createNftFeeInput) {
createNftFeeInput.value = tokenAtomicToDecimal(CREATE_NFT_FEE_ATOMIC);
}
if (createNftCreator) {
createNftCreator.textContent = activeWalletAddressShort || activeWalletAddressHex || "--";
}
if (createNftTypeLabel) {
createNftTypeLabel.textContent = isCollection ? "Collection" : "Standalone NFT";
}
if (createNftCountLabel) {
createNftCountLabel.textContent = countLabel;
}
if (createNftFeeLabel) {
createNftFeeLabel.textContent =
`${tokenAtomicToDecimal(CREATE_NFT_FEE_ATOMIC)} ${activeBaseCoin()}`;
}
}
function validateCreateNftForm() {
if (!walletLoaded) {
return "Load a wallet before creating an NFT.";
}
if (!registrationRegistered) {
return "Address registration is required before creating an NFT.";
}
const name = String(createNftNameInput?.value || "").trim();
if (!name) {
return "NFT name is required.";
}
if (!/^[A-Za-z0-9\s]+$/.test(name)) {
return "NFT name can only contain letters, numbers, and spaces.";
}
const canonicalName = name.replace(/\s+/g, "").toLowerCase();
if (canonicalName.length < 3 || canonicalName.length > 15) {
return "NFT name must normalize to 3 to 15 letters or numbers.";
}
if (canonicalName.toUpperCase() === activeBaseCoin()) {
return "NFT name is reserved for the base coin.";
}
const cid = String(createNftIpfsInput?.value || "").trim();
if (!cid) {
return "IPFS CID is required.";
}
if (!/^[\x20-\x7E]+$/.test(cid) || cid.length > 100) {
return "IPFS CID must be an ASCII value no longer than 100 characters.";
}
const description = String(createNftDescriptionInput?.value || "").trim();
if (description && (!/^[\x20-\x7E]+$/.test(description) || description.length > 100)) {
return "NFT description must be ASCII and no longer than 100 characters.";
}
const count = Number.parseInt(createNftCountInput?.value || "0", 10);
if (!Number.isInteger(count) || count < 1 || count > 4294967295) {
return "Enter a valid NFT item count.";
}
if (createNftSeriesValue() === 0 && count !== 1) {
return "A standalone NFT must create exactly 1 item.";
}
if (createNftSeriesValue() === 1 && count < 2) {
return "An NFT collection must contain at least 2 items.";
}
if (baseCoinBalanceAtomic() < CREATE_NFT_FEE_ATOMIC) {
return `This transaction requires ${tokenAtomicToDecimal(CREATE_NFT_FEE_ATOMIC)} ${activeBaseCoin()}.`;
}
return "";
}
function createNftPayload() {
return {
series: createNftSeriesValue(),
nftName: String(createNftNameInput?.value || "").trim(),
itemIpfs: String(createNftIpfsInput?.value || "").trim(),
count: Number.parseInt(createNftCountInput?.value || "1", 10),
description: String(createNftDescriptionInput?.value || "").trim(),
txfee: String(CREATE_NFT_FEE_ATOMIC)
};
}
function clearCreateNftForm() {
if (createNftSeriesSelect) {
createNftSeriesSelect.value = "0";
}
if (createNftNameInput) {
createNftNameInput.value = "";
}
if (createNftIpfsInput) {
createNftIpfsInput.value = "";
}
if (createNftCountInput) {
createNftCountInput.value = "1";
}
if (createNftDescriptionInput) {
createNftDescriptionInput.value = "";
}
updateCreateNftDerivedFields({ clampCount: true });
}
function setCreateNftButtonsDisabled(disabled) {
document.querySelector("[data-create-nft-save]")?.toggleAttribute("disabled", disabled);
document.querySelector("[data-create-nft-broadcast]")?.toggleAttribute("disabled", disabled);
}
async function handleCreateNftAction(action) {
if (!activeWalletBalances.length && walletLoaded && registrationRegistered) {
const invoke = window.__TAURI__?.core?.invoke;
const broadcastNode = activeBroadcastNode();
if (invoke && broadcastNode) {
createNftBalancesRefreshPromise ||= invoke("get_active_wallet_balances", { broadcastNode })
.then((balances) => {
activeWalletBalances = Array.isArray(balances) ? balances : [];
})
.finally(() => {
createNftBalancesRefreshPromise = null;
});
try {
await createNftBalancesRefreshPromise;
} catch (error) {
setCreateNftMessage(String(error), "error");
return;
}
}
}
const validationError = validateCreateNftForm();
if (validationError) {
setCreateNftMessage(validationError, "error");
return;
}
const invoke = window.__TAURI__?.core?.invoke;
if (!invoke) {
setCreateNftMessage("Wallet bridge is not available.", "error");
return;
}
const walletKey = await requestSigningWalletKey();
if (guiRuntimeSettings.require_key_before_signing !== false && !walletKey) {
setCreateNftMessage("Signing cancelled.");
return;
}
setCreateNftButtonsDisabled(true);
try {
if (action === "save") {
setCreateNftMessage("Saving NFT transaction...");
const review = await invoke("save_create_nft_transaction", {
...createNftPayload(),
walletKey
});
clearCreateNftForm();
setCreateNftMessage(`NFT transaction saved: ${review.file_name}`, "success");
if (typeof refreshUnbroadcastTransactions === "function") {
refreshUnbroadcastTransactions();
}
} else {
const broadcastNode = activeBroadcastNode();
if (!broadcastNode) {
throw new Error("Select a broadcast node before broadcasting.");
}
setCreateNftMessage("Broadcasting NFT transaction...");
const result = await invoke("broadcast_create_nft_transaction", {
...createNftPayload(),
broadcastNode,
walletKey
});
if (typeof invalidateDashboardNftBalanceKeys === "function") {
invalidateDashboardNftBalanceKeys();
}
clearCreateNftForm();
setCreateNftMessage(result.response || "NFT transaction broadcast.", "success");
}
} catch (error) {
setCreateNftMessage(String(error), "error");
} finally {
setCreateNftButtonsDisabled(false);
}
}
function prepareCreateNftForm() {
updateCreateNftDerivedFields({ clampCount: true });
setCreateNftMessage("");
}
function resetCreateNftSession() {
createNftBalancesRefreshPromise = null;
clearCreateNftForm();
setCreateNftMessage("");
}
createNftSeriesSelect?.addEventListener("change", () => {
updateCreateNftDerivedFields({ clampCount: true });
setCreateNftMessage("");
});
createNftCountInput?.addEventListener("input", () => {
updateCreateNftDerivedFields();
setCreateNftMessage("");
});
createNftCountInput?.addEventListener("blur", () => {
updateCreateNftDerivedFields({ clampCount: true });
});
createNftNameInput?.addEventListener("input", () => setCreateNftMessage(""));
createNftIpfsInput?.addEventListener("input", () => setCreateNftMessage(""));
createNftDescriptionInput?.addEventListener("input", () => setCreateNftMessage(""));
document
.querySelector("[data-create-nft-save]")
?.addEventListener("click", () => handleCreateNftAction("save"));
document
.querySelector("[data-create-nft-broadcast]")
?.addEventListener("click", () => handleCreateNftAction("broadcast"));