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"));