282 lines
8.7 KiB
JavaScript
282 lines
8.7 KiB
JavaScript
|
|
const ISSUE_TOKEN_FEE_ATOMIC = 10000000000n;
|
||
|
|
let issueTokenEligibility = null;
|
||
|
|
let issueTokenLookupId = 0;
|
||
|
|
|
||
|
|
function setIssueTokenMessage(message, type = "") {
|
||
|
|
setScopedTransactionMessage("Issue More Tokens", issueTokenMessage, message, type);
|
||
|
|
}
|
||
|
|
|
||
|
|
function setIssueTokenButtonsDisabled(disabled) {
|
||
|
|
document.querySelector("[data-issue-token-save]")?.toggleAttribute("disabled", disabled);
|
||
|
|
document.querySelector("[data-issue-token-broadcast]")?.toggleAttribute("disabled", disabled);
|
||
|
|
}
|
||
|
|
|
||
|
|
function updateIssueTokenFee() {
|
||
|
|
const displayFee = tokenAtomicToDecimal(ISSUE_TOKEN_FEE_ATOMIC);
|
||
|
|
if (issueTokenFeeInput) {
|
||
|
|
issueTokenFeeInput.value = displayFee;
|
||
|
|
}
|
||
|
|
if (issueTokenFeeLabel) {
|
||
|
|
issueTokenFeeLabel.textContent = `${displayFee} ${activeBaseCoin()}`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function resetIssueTokenDetails() {
|
||
|
|
issueTokenEligibility = null;
|
||
|
|
if (issueTokenCreator) {
|
||
|
|
issueTokenCreator.textContent = "--";
|
||
|
|
}
|
||
|
|
if (issueTokenPolicy) {
|
||
|
|
issueTokenPolicy.textContent = "--";
|
||
|
|
}
|
||
|
|
setIssueTokenButtonsDisabled(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
async function refreshIssueTokenEligibility() {
|
||
|
|
const invoke = window.__TAURI__?.core?.invoke;
|
||
|
|
const broadcastNode = activeBroadcastNode();
|
||
|
|
const walletAddress = activeWalletAddressShort || activeWalletAddressHex || "";
|
||
|
|
const ticker = String(issueTokenTickerSelect?.value || "").trim();
|
||
|
|
const lookupId = ++issueTokenLookupId;
|
||
|
|
resetIssueTokenDetails();
|
||
|
|
|
||
|
|
if (!ticker || !invoke || !broadcastNode || !walletLoaded) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
setIssueTokenMessage("Checking token issuance policy...");
|
||
|
|
try {
|
||
|
|
const details = await invokeWalletRpcWithBackoff(
|
||
|
|
"Token details lookup",
|
||
|
|
() => invoke("issue_token_details", { broadcastNode, ticker }),
|
||
|
|
() =>
|
||
|
|
lookupId === issueTokenLookupId &&
|
||
|
|
walletLoaded &&
|
||
|
|
(activeWalletAddressShort || activeWalletAddressHex || "") === walletAddress &&
|
||
|
|
activeBroadcastNode() === broadcastNode &&
|
||
|
|
issueTokenTickerSelect?.value === ticker
|
||
|
|
);
|
||
|
|
if (lookupId !== issueTokenLookupId) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
issueTokenEligibility = details;
|
||
|
|
if (issueTokenCreator) {
|
||
|
|
issueTokenCreator.textContent = details.creator || "--";
|
||
|
|
}
|
||
|
|
if (issueTokenPolicy) {
|
||
|
|
issueTokenPolicy.textContent = details.hard_limit
|
||
|
|
? "Hard capped"
|
||
|
|
: "Future issuance allowed";
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!details.eligible) {
|
||
|
|
setIssueTokenMessage(
|
||
|
|
details.reason || "This token cannot be issued by the active wallet.",
|
||
|
|
"error"
|
||
|
|
);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
setIssueTokenMessage("The active wallet may issue more of this token.", "success");
|
||
|
|
setIssueTokenButtonsDisabled(false);
|
||
|
|
} catch (error) {
|
||
|
|
if (lookupId !== issueTokenLookupId) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
setIssueTokenMessage(String(error), "error");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function refreshIssueTokenList() {
|
||
|
|
issueTokenLookupId += 1;
|
||
|
|
const invoke = window.__TAURI__?.core?.invoke;
|
||
|
|
const broadcastNode = activeBroadcastNode();
|
||
|
|
const walletAddress = activeWalletAddressShort || activeWalletAddressHex || "";
|
||
|
|
resetIssueTokenDetails();
|
||
|
|
if (!issueTokenTickerSelect) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
issueTokenTickerSelect.innerHTML = "";
|
||
|
|
const loadingOption = document.createElement("option");
|
||
|
|
loadingOption.value = "";
|
||
|
|
loadingOption.textContent = "Loading tokens...";
|
||
|
|
issueTokenTickerSelect.appendChild(loadingOption);
|
||
|
|
|
||
|
|
if (!invoke || !walletLoaded || !broadcastNode) {
|
||
|
|
loadingOption.textContent = "Load a wallet and select a node";
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const tokens = await invokeWalletRpcWithBackoff(
|
||
|
|
"Token list lookup",
|
||
|
|
() => invoke("token_list", { broadcastNode }),
|
||
|
|
() =>
|
||
|
|
walletLoaded &&
|
||
|
|
(activeWalletAddressShort || activeWalletAddressHex || "") === walletAddress &&
|
||
|
|
activeBroadcastNode() === broadcastNode
|
||
|
|
);
|
||
|
|
if (
|
||
|
|
!walletLoaded ||
|
||
|
|
(activeWalletAddressShort || activeWalletAddressHex || "") !== walletAddress ||
|
||
|
|
activeBroadcastNode() !== broadcastNode
|
||
|
|
) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
issueTokenTickerSelect.innerHTML = "";
|
||
|
|
const tokenItems = Array.isArray(tokens) ? tokens : [];
|
||
|
|
if (!tokenItems.length) {
|
||
|
|
const option = document.createElement("option");
|
||
|
|
option.value = "";
|
||
|
|
option.textContent = "No tokens exist";
|
||
|
|
issueTokenTickerSelect.appendChild(option);
|
||
|
|
setIssueTokenMessage("No tokens are available to issue.", "error");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
tokenItems.forEach((token) => {
|
||
|
|
const ticker = String(token?.ticker || "").trim();
|
||
|
|
if (!ticker) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const option = document.createElement("option");
|
||
|
|
option.value = ticker;
|
||
|
|
option.textContent = ticker;
|
||
|
|
issueTokenTickerSelect.appendChild(option);
|
||
|
|
});
|
||
|
|
await refreshIssueTokenEligibility();
|
||
|
|
} catch (error) {
|
||
|
|
issueTokenTickerSelect.innerHTML = "";
|
||
|
|
const option = document.createElement("option");
|
||
|
|
option.value = "";
|
||
|
|
option.textContent = "Token list unavailable";
|
||
|
|
issueTokenTickerSelect.appendChild(option);
|
||
|
|
setIssueTokenMessage(String(error), "error");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function validateIssueTokenForm() {
|
||
|
|
if (!walletLoaded) {
|
||
|
|
return "Load a wallet before issuing tokens.";
|
||
|
|
}
|
||
|
|
if (!registrationRegistered) {
|
||
|
|
return "Address registration is required before issuing tokens.";
|
||
|
|
}
|
||
|
|
if (!issueTokenEligibility?.eligible) {
|
||
|
|
return issueTokenEligibility?.reason || "Select a token the active wallet may issue.";
|
||
|
|
}
|
||
|
|
|
||
|
|
const number = tokenDecimalToAtomic(issueTokenNumberInput?.value);
|
||
|
|
if (number === null || number <= 0n) {
|
||
|
|
return "Enter a valid additional supply with up to 8 decimal places.";
|
||
|
|
}
|
||
|
|
|
||
|
|
const baseCoin = activeBaseCoin();
|
||
|
|
const baseBalance = (Array.isArray(activeWalletBalances) ? activeWalletBalances : []).find(
|
||
|
|
(balance) =>
|
||
|
|
String(balance.asset || "").trim().toUpperCase() === baseCoin &&
|
||
|
|
!Number(balance.nft_series || 0)
|
||
|
|
);
|
||
|
|
if (baseBalance && balanceAtomicValue(baseBalance) < ISSUE_TOKEN_FEE_ATOMIC) {
|
||
|
|
return `This transaction requires ${tokenAtomicToDecimal(ISSUE_TOKEN_FEE_ATOMIC)} ${baseCoin}.`;
|
||
|
|
}
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
function issueTokenPayload() {
|
||
|
|
return {
|
||
|
|
broadcastNode: activeBroadcastNode(),
|
||
|
|
ticker: String(issueTokenTickerSelect?.value || "").trim(),
|
||
|
|
number: String(tokenDecimalToAtomic(issueTokenNumberInput?.value) || 0n),
|
||
|
|
txfee: String(ISSUE_TOKEN_FEE_ATOMIC)
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function clearIssueTokenForm() {
|
||
|
|
if (issueTokenNumberInput) {
|
||
|
|
issueTokenNumberInput.value = "";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function handleIssueTokenAction(action) {
|
||
|
|
const error = validateIssueTokenForm();
|
||
|
|
if (error) {
|
||
|
|
setIssueTokenMessage(error, "error");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const invoke = window.__TAURI__?.core?.invoke;
|
||
|
|
if (!invoke) {
|
||
|
|
setIssueTokenMessage("Wallet bridge is not available.", "error");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const walletKey = await requestSigningWalletKey();
|
||
|
|
if (guiRuntimeSettings.require_key_before_signing !== false && !walletKey) {
|
||
|
|
setIssueTokenMessage("Signing cancelled.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
setIssueTokenButtonsDisabled(true);
|
||
|
|
try {
|
||
|
|
if (action === "save") {
|
||
|
|
setIssueTokenMessage("Saving issue-token transaction...");
|
||
|
|
const review = await invoke("save_issue_token_transaction", {
|
||
|
|
...issueTokenPayload(),
|
||
|
|
walletKey
|
||
|
|
});
|
||
|
|
clearIssueTokenForm();
|
||
|
|
setIssueTokenMessage(`Issue-token transaction saved: ${review.file_name}`, "success");
|
||
|
|
if (typeof refreshUnbroadcastTransactions === "function") {
|
||
|
|
refreshUnbroadcastTransactions();
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
setIssueTokenMessage("Broadcasting issue-token transaction...");
|
||
|
|
const result = await invoke("broadcast_issue_token_transaction", {
|
||
|
|
...issueTokenPayload(),
|
||
|
|
walletKey
|
||
|
|
});
|
||
|
|
if (typeof invalidateAssetsCatalog === "function") {
|
||
|
|
invalidateAssetsCatalog();
|
||
|
|
}
|
||
|
|
clearIssueTokenForm();
|
||
|
|
setIssueTokenMessage(result.response || "Issue-token transaction broadcast.", "success");
|
||
|
|
}
|
||
|
|
} catch (actionError) {
|
||
|
|
setIssueTokenMessage(String(actionError), "error");
|
||
|
|
} finally {
|
||
|
|
setIssueTokenButtonsDisabled(!issueTokenEligibility?.eligible);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function prepareIssueTokenForm() {
|
||
|
|
updateIssueTokenFee();
|
||
|
|
setIssueTokenMessage("");
|
||
|
|
refreshIssueTokenList();
|
||
|
|
}
|
||
|
|
|
||
|
|
function resetIssueTokenSession() {
|
||
|
|
issueTokenLookupId += 1;
|
||
|
|
resetIssueTokenDetails();
|
||
|
|
if (issueTokenTickerSelect) {
|
||
|
|
issueTokenTickerSelect.innerHTML = '<option value="">Load a wallet first</option>';
|
||
|
|
}
|
||
|
|
clearIssueTokenForm();
|
||
|
|
setIssueTokenMessage("");
|
||
|
|
}
|
||
|
|
|
||
|
|
issueTokenTickerSelect?.addEventListener("change", refreshIssueTokenEligibility);
|
||
|
|
issueTokenNumberInput?.addEventListener("input", () => {
|
||
|
|
if (issueTokenEligibility?.eligible) {
|
||
|
|
setIssueTokenMessage("");
|
||
|
|
}
|
||
|
|
});
|
||
|
|
document
|
||
|
|
.querySelector("[data-issue-token-save]")
|
||
|
|
?.addEventListener("click", () => handleIssueTokenAction("save"));
|
||
|
|
document
|
||
|
|
.querySelector("[data-issue-token-broadcast]")
|
||
|
|
?.addEventListener("click", () => handleIssueTokenAction("broadcast"));
|