274 lines
7.8 KiB
JavaScript
274 lines
7.8 KiB
JavaScript
|
|
const BURN_FEE_ATOMIC = 10000n;
|
||
|
|
let burnBalancesRefreshPromise = null;
|
||
|
|
|
||
|
|
function setBurnMessage(message, type = "") {
|
||
|
|
setScopedTransactionMessage("Burn", burnMessage, message, type);
|
||
|
|
}
|
||
|
|
|
||
|
|
function burnableBalances() {
|
||
|
|
return (Array.isArray(activeWalletBalances) ? activeWalletBalances : [])
|
||
|
|
.filter((balance) => {
|
||
|
|
const asset = String(balance.asset || "").trim();
|
||
|
|
return asset
|
||
|
|
&& !isBaseTransferAsset(asset)
|
||
|
|
&& balanceAtomicValue(balance) > 0n;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function burnAssetOptionLabel(balance) {
|
||
|
|
const amount = formatAtomicBalance(balance.balance || "0");
|
||
|
|
const series = Number(balance.nft_series || 0);
|
||
|
|
if (series > 0) {
|
||
|
|
return `${balance.asset} #${series} (${amount})`;
|
||
|
|
}
|
||
|
|
return `${balance.asset} (${amount})`;
|
||
|
|
}
|
||
|
|
|
||
|
|
function selectedBurnAsset() {
|
||
|
|
const option = burnAssetSelect?.selectedOptions?.[0];
|
||
|
|
if (!option?.value) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
asset: option.dataset.asset || "",
|
||
|
|
nftSeries: Number(option.dataset.nftSeries || 0),
|
||
|
|
balance: BigInt(option.dataset.balance || "0")
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function populateBurnAssets() {
|
||
|
|
if (!burnAssetSelect) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const previous = burnAssetSelect.value;
|
||
|
|
burnAssetSelect.innerHTML = "";
|
||
|
|
const balances = burnableBalances();
|
||
|
|
if (balances.length) {
|
||
|
|
const placeholder = document.createElement("option");
|
||
|
|
placeholder.value = "";
|
||
|
|
placeholder.textContent = "Select an asset";
|
||
|
|
burnAssetSelect.appendChild(placeholder);
|
||
|
|
}
|
||
|
|
balances.forEach((balance) => {
|
||
|
|
const option = document.createElement("option");
|
||
|
|
option.value = transferAssetKey(balance);
|
||
|
|
option.dataset.asset = balance.asset || "";
|
||
|
|
option.dataset.nftSeries = String(balance.nft_series || 0);
|
||
|
|
option.dataset.balance = String(balance.balance || "0");
|
||
|
|
option.textContent = burnAssetOptionLabel(balance);
|
||
|
|
burnAssetSelect.appendChild(option);
|
||
|
|
});
|
||
|
|
if (!balances.length) {
|
||
|
|
const option = document.createElement("option");
|
||
|
|
option.value = "";
|
||
|
|
option.textContent = "No burnable assets";
|
||
|
|
burnAssetSelect.appendChild(option);
|
||
|
|
} else if ([...burnAssetSelect.options].some((option) => option.value === previous)) {
|
||
|
|
burnAssetSelect.value = previous;
|
||
|
|
}
|
||
|
|
updateBurnDerivedFields();
|
||
|
|
}
|
||
|
|
|
||
|
|
async function ensureBurnBalances() {
|
||
|
|
if (activeWalletBalances.length || !walletLoaded || !registrationRegistered) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const invoke = window.__TAURI__?.core?.invoke;
|
||
|
|
const broadcastNode = activeBroadcastNode();
|
||
|
|
if (!invoke || !broadcastNode) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
burnBalancesRefreshPromise ||= invoke("get_active_wallet_balances", { broadcastNode })
|
||
|
|
.then((balances) => {
|
||
|
|
activeWalletBalances = Array.isArray(balances) ? balances : [];
|
||
|
|
})
|
||
|
|
.finally(() => {
|
||
|
|
burnBalancesRefreshPromise = null;
|
||
|
|
});
|
||
|
|
await burnBalancesRefreshPromise;
|
||
|
|
}
|
||
|
|
|
||
|
|
function updateBurnDerivedFields() {
|
||
|
|
const selected = selectedBurnAsset();
|
||
|
|
if (burnFeeInput) {
|
||
|
|
burnFeeInput.value = atomicToDecimal(BURN_FEE_ATOMIC);
|
||
|
|
}
|
||
|
|
if (burnFeeLabel) {
|
||
|
|
burnFeeLabel.textContent = `${atomicToDecimal(BURN_FEE_ATOMIC)} ${activeBaseCoin()}`;
|
||
|
|
}
|
||
|
|
if (burnAddress) {
|
||
|
|
burnAddress.textContent = activeWalletAddressShort || activeWalletAddressHex || "--";
|
||
|
|
}
|
||
|
|
if (!selected) {
|
||
|
|
if (burnAvailable) {
|
||
|
|
burnAvailable.textContent = "--";
|
||
|
|
}
|
||
|
|
if (burnType) {
|
||
|
|
burnType.textContent = "--";
|
||
|
|
}
|
||
|
|
if (burnAmountInput) {
|
||
|
|
burnAmountInput.disabled = false;
|
||
|
|
burnAmountInput.value = "";
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const isNft = selected.nftSeries > 0;
|
||
|
|
if (burnAvailable) {
|
||
|
|
burnAvailable.textContent = `${atomicToDecimal(selected.balance)} ${selected.asset}`;
|
||
|
|
}
|
||
|
|
if (burnType) {
|
||
|
|
burnType.textContent = isNft ? `NFT #${selected.nftSeries}` : "Token";
|
||
|
|
}
|
||
|
|
if (burnAmountInput) {
|
||
|
|
burnAmountInput.disabled = isNft;
|
||
|
|
if (isNft) {
|
||
|
|
burnAmountInput.value = "1.00000000";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function validateBurnForm() {
|
||
|
|
if (!walletLoaded) {
|
||
|
|
return "Load a wallet before burning an asset.";
|
||
|
|
}
|
||
|
|
if (!registrationRegistered) {
|
||
|
|
return "Address registration is required before burning an asset.";
|
||
|
|
}
|
||
|
|
const selected = selectedBurnAsset();
|
||
|
|
if (!selected) {
|
||
|
|
return "Select an asset to burn.";
|
||
|
|
}
|
||
|
|
const amount = decimalToAtomic(burnAmountInput?.value);
|
||
|
|
if (amount === null || amount <= 0n) {
|
||
|
|
return "Enter a valid burn amount.";
|
||
|
|
}
|
||
|
|
if (selected.nftSeries > 0 && amount !== ATOMIC_UNITS_PER_COIN) {
|
||
|
|
return "NFT burns must destroy exactly 1.00000000 units.";
|
||
|
|
}
|
||
|
|
if (amount > selected.balance) {
|
||
|
|
return "Burn amount cannot exceed the selected asset balance.";
|
||
|
|
}
|
||
|
|
if (baseCoinBalanceAtomic() < BURN_FEE_ATOMIC) {
|
||
|
|
return `Burning requires ${atomicToDecimal(BURN_FEE_ATOMIC)} ${activeBaseCoin()} for the transaction fee.`;
|
||
|
|
}
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
function burnPayload() {
|
||
|
|
const selected = selectedBurnAsset();
|
||
|
|
return {
|
||
|
|
coin: selected.asset,
|
||
|
|
nftSeries: selected.nftSeries,
|
||
|
|
value: String(decimalToAtomic(burnAmountInput.value)),
|
||
|
|
txfee: String(BURN_FEE_ATOMIC)
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function clearBurnForm() {
|
||
|
|
if (burnAssetSelect) {
|
||
|
|
burnAssetSelect.value = "";
|
||
|
|
}
|
||
|
|
if (burnAmountInput) {
|
||
|
|
burnAmountInput.value = "";
|
||
|
|
burnAmountInput.disabled = false;
|
||
|
|
}
|
||
|
|
updateBurnDerivedFields();
|
||
|
|
}
|
||
|
|
|
||
|
|
function setBurnButtonsDisabled(disabled) {
|
||
|
|
document.querySelector("[data-burn-save]")?.toggleAttribute("disabled", disabled);
|
||
|
|
document.querySelector("[data-burn-broadcast]")?.toggleAttribute("disabled", disabled);
|
||
|
|
}
|
||
|
|
|
||
|
|
async function handleBurnAction(action) {
|
||
|
|
try {
|
||
|
|
await ensureBurnBalances();
|
||
|
|
populateBurnAssets();
|
||
|
|
} catch (error) {
|
||
|
|
setBurnMessage(String(error), "error");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const validationError = validateBurnForm();
|
||
|
|
if (validationError) {
|
||
|
|
setBurnMessage(validationError, "error");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const invoke = window.__TAURI__?.core?.invoke;
|
||
|
|
if (!invoke) {
|
||
|
|
setBurnMessage("Wallet bridge is not available.", "error");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const walletKey = await requestSigningWalletKey();
|
||
|
|
if (guiRuntimeSettings.require_key_before_signing !== false && !walletKey) {
|
||
|
|
setBurnMessage("Signing cancelled.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
setBurnButtonsDisabled(true);
|
||
|
|
try {
|
||
|
|
if (action === "save") {
|
||
|
|
setBurnMessage("Saving burn transaction...");
|
||
|
|
const review = await invoke("save_burn_transaction", {
|
||
|
|
...burnPayload(),
|
||
|
|
walletKey
|
||
|
|
});
|
||
|
|
clearBurnForm();
|
||
|
|
setBurnMessage(`Burn 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.");
|
||
|
|
}
|
||
|
|
setBurnMessage("Broadcasting burn transaction...");
|
||
|
|
const result = await invoke("broadcast_burn_transaction", {
|
||
|
|
...burnPayload(),
|
||
|
|
broadcastNode,
|
||
|
|
walletKey
|
||
|
|
});
|
||
|
|
if (typeof invalidateAssetsCatalog === "function") {
|
||
|
|
invalidateAssetsCatalog();
|
||
|
|
}
|
||
|
|
activeWalletBalances = [];
|
||
|
|
clearBurnForm();
|
||
|
|
setBurnMessage(result.response || "Burn transaction broadcast.", "success");
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
setBurnMessage(String(error), "error");
|
||
|
|
} finally {
|
||
|
|
setBurnButtonsDisabled(false);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function prepareBurnForm() {
|
||
|
|
setBurnMessage("");
|
||
|
|
try {
|
||
|
|
await ensureBurnBalances();
|
||
|
|
} catch (error) {
|
||
|
|
setBurnMessage(String(error), "error");
|
||
|
|
}
|
||
|
|
populateBurnAssets();
|
||
|
|
}
|
||
|
|
|
||
|
|
function resetBurnSession() {
|
||
|
|
burnBalancesRefreshPromise = null;
|
||
|
|
clearBurnForm();
|
||
|
|
setBurnMessage("");
|
||
|
|
}
|
||
|
|
|
||
|
|
burnAssetSelect?.addEventListener("change", () => {
|
||
|
|
setBurnMessage("");
|
||
|
|
updateBurnDerivedFields();
|
||
|
|
});
|
||
|
|
burnAmountInput?.addEventListener("input", () => setBurnMessage(""));
|
||
|
|
document
|
||
|
|
.querySelector("[data-burn-save]")
|
||
|
|
?.addEventListener("click", () => handleBurnAction("save"));
|
||
|
|
document
|
||
|
|
.querySelector("[data-burn-broadcast]")
|
||
|
|
?.addEventListener("click", () => handleBurnAction("broadcast"));
|