const DATA_STORAGE_KEY_FEE_ATOMIC = 50000000000n; const DATA_STORAGE_VALUE_FEE_ATOMIC = 100000000n; const DATA_STORAGE_U64_MAX = 18446744073709551615n; const DATA_STORAGE_U128_MAX = 340282366920938463463374607431768211455n; const DATA_STORAGE_I64_MIN = -9223372036854775808n; const DATA_STORAGE_I64_MAX = 9223372036854775807n; const DATA_STORAGE_I128_MIN = -170141183460469231731687303715884105728n; const DATA_STORAGE_I128_MAX = 170141183460469231731687303715884105727n; const DATA_STORAGE_NEW_KEY_OPTION = "__new__"; function setDataStorageMessage(message, type = "") { setScopedTransactionMessage("Data Storage", dataStorageMessage, message, type); } function isDataStorageKeyMode() { return dataStorageModeSelect?.value !== "write_value"; } function dataStorageFeeAtomic() { return isDataStorageKeyMode() ? DATA_STORAGE_KEY_FEE_ATOMIC : DATA_STORAGE_VALUE_FEE_ATOMIC; } function dataStorageValueTypeName() { return inferDataStorageValueType(dataStorageValueInput?.value); } function inferDataStorageValueType(value) { const text = String(value ?? "").trim(); if (!text) { return ""; } const normalized = text.toLowerCase(); if (["true", "false", "yes", "no"].includes(normalized)) { return "bool"; } if (/^\+?\d+$/.test(text)) { const number = BigInt(text); if (number <= 255n) return "u8"; if (number <= 65535n) return "u16"; if (number <= 4294967295n) return "u32"; if (number <= DATA_STORAGE_U64_MAX) return "u64"; if (number <= DATA_STORAGE_U128_MAX) return "u128"; return "string"; } if (/^-\d+$/.test(text)) { const number = BigInt(text); if (number >= -128n) return "i8"; if (number >= -32768n) return "i16"; if (number >= -2147483648n) return "i32"; if (number >= DATA_STORAGE_I64_MIN) return "i64"; if (number >= DATA_STORAGE_I128_MIN) return "i128"; return "string"; } return "string"; } function dataStorageFieldIsAscii(value) { return /^[\x20-\x7E]+$/.test(value); } function dataStorageHashIsValid(value) { return /^[0-9a-fA-F]{64}$/.test(String(value || "").trim()); } function dataStoragePreviousHashValue() { const text = String(dataStoragePreviousInput?.value || "").trim(); return text === "" ? "0" : text; } function dataStorageUsesNewKey() { return dataStorageKeySelect?.value === DATA_STORAGE_NEW_KEY_OPTION; } function selectedDataStorageKey() { if (dataStorageUsesNewKey()) { return String(dataStorageKeyInput?.value || "").trim(); } return String(dataStorageKeySelect?.value || "").trim(); } async function refreshDataStorageKeyOptions() { if (!dataStorageKeySelect || isDataStorageKeyMode()) { return; } const previousSelection = dataStorageKeySelect.value; dataStorageKeySelect.disabled = true; dataStorageKeySelect.replaceChildren(new Option("Loading storage keys...", "")); let knownKeys = []; try { if (typeof knownAddressDataStorageKeys === "function") { knownKeys = await knownAddressDataStorageKeys(); } } catch (_) { knownKeys = []; } knownKeys.sort((left, right) => { const leftName = left.label || left.storageId; const rightName = right.label || right.storageId; return leftName.localeCompare(rightName); }); const options = [new Option("Select storage key", "")]; knownKeys.forEach(({ storageId, label }) => { options.push(new Option(label || storageId, storageId)); }); options.push(new Option("Enter new storage key", DATA_STORAGE_NEW_KEY_OPTION)); dataStorageKeySelect.replaceChildren(...options); dataStorageKeySelect.disabled = false; const selectionStillExists = Array.from(dataStorageKeySelect.options) .some((option) => option.value === previousSelection); dataStorageKeySelect.value = selectionStillExists ? previousSelection : ""; updateDataStorageDerivedFields(); } function dataStorageParseBigInt(value, max, label) { const text = String(value || "").trim(); if (!/^\+?\d+$/.test(text)) { return { error: `${label} must be a whole number.` }; } const number = BigInt(text); if (number > max) { return { error: `${label} is too large for ${dataStorageValueTypeName().toUpperCase()}.` }; } return { value: number.toString() }; } function dataStorageParseSignedBigInt(value, min, max, label) { const text = String(value || "").trim(); if (!/^-?\d+$/.test(text)) { return { error: `${label} must be a whole number.` }; } const number = BigInt(text); if (number < min || number > max) { return { error: `${label} is outside the ${dataStorageValueTypeName().toUpperCase()} range.` }; } return { value: number.toString() }; } function dataStorageNormalizedBool(value) { const text = String(value || "").trim().toLowerCase(); if (["true", "1", "yes"].includes(text)) { return { value: "true" }; } if (["false", "0", "no"].includes(text)) { return { value: "false" }; } return { error: "Bool value must be true or false." }; } function updateDataStorageDerivedFields() { const keyMode = isDataStorageKeyMode(); const inferredType = dataStorageValueTypeName(); const stringMode = !keyMode && inferredType === "string"; const fee = dataStorageFeeAtomic(); dataStorageTypeRow?.classList.toggle("hidden", keyMode); dataStorageKeyChoiceRow?.classList.toggle("hidden", keyMode); dataStorageKeyRow?.classList.toggle("hidden", keyMode || !dataStorageUsesNewKey()); dataStorageFieldRow?.classList.toggle("hidden", keyMode); dataStorageValueRow?.classList.toggle("hidden", keyMode); dataStoragePreviousRow?.classList.toggle("hidden", !stringMode); if (dataStorageTypeSelect) { dataStorageTypeSelect.value = keyMode ? "" : inferredType; } if (dataStorageFeeInput) { dataStorageFeeInput.value = atomicToDecimal(fee); } if (dataStorageWriter) { dataStorageWriter.textContent = activeWalletAddressShort || activeWalletAddressHex || "--"; } if (dataStorageActionLabel) { dataStorageActionLabel.textContent = keyMode ? "Create storage key" : "Write storage value"; } if (dataStorageTypeLabel) { dataStorageTypeLabel.textContent = keyMode || !inferredType ? "--" : inferredType.toUpperCase(); } if (dataStorageFeeLabel) { dataStorageFeeLabel.textContent = `${atomicToDecimal(fee)} ${activeBaseCoin()}`; } } function validateDataStorageForm() { if (!walletLoaded) { return "Load a wallet before creating a data storage transaction."; } if (!registrationRegistered) { return "Address registration is required before creating a data storage transaction."; } if (baseCoinBalanceAtomic() < dataStorageFeeAtomic()) { return `Data storage requires ${atomicToDecimal(dataStorageFeeAtomic())} ${activeBaseCoin()} for the fee.`; } if (isDataStorageKeyMode()) { return ""; } const storageKey = selectedDataStorageKey(); if (!storageKey) { return "Select a storage key or choose Enter new storage key."; } if (!dataStorageHashIsValid(storageKey)) { return "Storage key hash must be exactly 64 hexadecimal characters."; } const fieldKey = String(dataStorageFieldKeyInput?.value || "").trim(); if (!fieldKey) { return "Data key is required."; } if (fieldKey.length > 50 || !dataStorageFieldIsAscii(fieldKey)) { return "Data key must be ASCII and no longer than 50 characters."; } const value = String(dataStorageValueInput?.value || ""); const type = dataStorageValueTypeName(); if (!type) { return "Value is required."; } if (type === "bool") { return dataStorageNormalizedBool(value).error || ""; } if (type === "u8") { return dataStorageParseBigInt(value, 255n, "U8 value").error || ""; } if (type === "u16") { return dataStorageParseBigInt(value, 65535n, "U16 value").error || ""; } if (type === "u32") { return dataStorageParseBigInt(value, 4294967295n, "U32 value").error || ""; } if (type === "u64") { return dataStorageParseBigInt(value, DATA_STORAGE_U64_MAX, "U64 value").error || ""; } if (type === "u128") { return dataStorageParseBigInt(value, DATA_STORAGE_U128_MAX, "U128 value").error || ""; } if (type === "i8") { return dataStorageParseSignedBigInt(value, -128n, 127n, "I8 value").error || ""; } if (type === "i16") { return dataStorageParseSignedBigInt(value, -32768n, 32767n, "I16 value").error || ""; } if (type === "i32") { return dataStorageParseSignedBigInt(value, -2147483648n, 2147483647n, "I32 value").error || ""; } if (type === "i64") { return dataStorageParseSignedBigInt(value, DATA_STORAGE_I64_MIN, DATA_STORAGE_I64_MAX, "I64 value").error || ""; } if (type === "i128") { return dataStorageParseSignedBigInt(value, DATA_STORAGE_I128_MIN, DATA_STORAGE_I128_MAX, "I128 value").error || ""; } if (type === "string") { if (!dataStorageFieldIsAscii(value) || value.length > 180) { return "String value must be ASCII and no longer than 180 characters."; } const previous = dataStoragePreviousHashValue(); if (previous !== "0" && !dataStorageHashIsValid(previous)) { return "Previous string transaction must be 0 or a 64 character hash."; } return ""; } return "Select a valid storage value type."; } function normalizedDataStorageValue() { const value = String(dataStorageValueInput?.value || "").trim(); const type = dataStorageValueTypeName(); if (type === "bool") { return dataStorageNormalizedBool(value).value; } if (type === "u8") { return dataStorageParseBigInt(value, 255n, "U8 value").value; } if (type === "u16") { return dataStorageParseBigInt(value, 65535n, "U16 value").value; } if (type === "u32") { return dataStorageParseBigInt(value, 4294967295n, "U32 value").value; } if (type === "u64") { return dataStorageParseBigInt(value, DATA_STORAGE_U64_MAX, "U64 value").value; } if (type === "u128") { return dataStorageParseBigInt(value, DATA_STORAGE_U128_MAX, "U128 value").value; } if (type === "i8") { return dataStorageParseSignedBigInt(value, -128n, 127n, "I8 value").value; } if (type === "i16") { return dataStorageParseSignedBigInt(value, -32768n, 32767n, "I16 value").value; } if (type === "i32") { return dataStorageParseSignedBigInt(value, -2147483648n, 2147483647n, "I32 value").value; } if (type === "i64") { return dataStorageParseSignedBigInt(value, DATA_STORAGE_I64_MIN, DATA_STORAGE_I64_MAX, "I64 value").value; } if (type === "i128") { return dataStorageParseSignedBigInt(value, DATA_STORAGE_I128_MIN, DATA_STORAGE_I128_MAX, "I128 value").value; } return String(dataStorageValueInput?.value || "").trim(); } function dataStoragePayload() { return { mode: dataStorageModeSelect.value, valueType: dataStorageValueTypeName(), storageKey: selectedDataStorageKey(), fieldKey: String(dataStorageFieldKeyInput?.value || "").trim(), value: isDataStorageKeyMode() ? "" : normalizedDataStorageValue(), previousHash: isDataStorageKeyMode() ? "0" : dataStoragePreviousHashValue(), txfee: String(dataStorageFeeAtomic()) }; } function clearDataStorageForm() { if (dataStorageModeSelect) { dataStorageModeSelect.value = "create_key"; } if (dataStorageTypeSelect) { dataStorageTypeSelect.value = ""; } if (dataStorageKeySelect) { dataStorageKeySelect.value = ""; } if (dataStorageKeyInput) { dataStorageKeyInput.value = ""; } if (dataStorageFieldKeyInput) { dataStorageFieldKeyInput.value = ""; } if (dataStorageValueInput) { dataStorageValueInput.value = ""; } if (dataStoragePreviousInput) { dataStoragePreviousInput.value = ""; } updateDataStorageDerivedFields(); } function setDataStorageButtonsDisabled(disabled) { document.querySelector("[data-data-storage-save]")?.toggleAttribute("disabled", disabled); document.querySelector("[data-data-storage-broadcast]")?.toggleAttribute("disabled", disabled); } async function handleDataStorageAction(action) { const validationError = validateDataStorageForm(); if (validationError) { setDataStorageMessage(validationError, "error"); return; } const invoke = window.__TAURI__?.core?.invoke; if (!invoke) { setDataStorageMessage("Wallet bridge is not available.", "error"); return; } const walletKey = await requestSigningWalletKey(); if (guiRuntimeSettings.require_key_before_signing !== false && !walletKey) { setDataStorageMessage("Signing cancelled."); return; } setDataStorageButtonsDisabled(true); try { if (action === "save") { setDataStorageMessage("Saving data storage transaction..."); const review = await invoke("save_data_storage_transaction", { ...dataStoragePayload(), walletKey }); clearDataStorageForm(); setDataStorageMessage(`Data storage 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."); } setDataStorageMessage("Broadcasting data storage transaction..."); const result = await invoke("broadcast_data_storage_transaction", { ...dataStoragePayload(), broadcastNode, walletKey }); clearDataStorageForm(); setDataStorageMessage(result.response || "Data storage transaction broadcast.", "success"); } } catch (error) { setDataStorageMessage(String(error), "error"); } finally { setDataStorageButtonsDisabled(false); } } function prepareDataStorageForm() { updateDataStorageDerivedFields(); refreshDataStorageKeyOptions(); setDataStorageMessage(""); } function resetDataStorageSession() { clearDataStorageForm(); setDataStorageMessage(""); } [ dataStorageModeSelect, dataStorageTypeSelect, dataStorageKeySelect, dataStorageKeyInput, dataStorageFieldKeyInput, dataStorageValueInput, dataStoragePreviousInput ].forEach((element) => { element?.addEventListener("input", () => { updateDataStorageDerivedFields(); setDataStorageMessage(""); }); element?.addEventListener("change", () => { updateDataStorageDerivedFields(); setDataStorageMessage(""); }); }); dataStorageModeSelect?.addEventListener("change", () => { refreshDataStorageKeyOptions(); }); document .querySelector("[data-data-storage-save]") ?.addEventListener("click", () => handleDataStorageAction("save")); document .querySelector("[data-data-storage-broadcast]") ?.addEventListener("click", () => handleDataStorageAction("broadcast"));