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

900 lines
42 KiB
JavaScript
Raw Normal View History

2026-07-01 16:01:30 +00:00
const titles = {
dashboard: ["Dashboard", "Overview for the active address."],
send: ["Send", "Create and preview transaction forms."],
receive: ["Receive", "Show wallet address and payment request details."],
history: ["History", "Full local transaction history for the active address."],
transactions: ["Transactions", "Review local and confirmed transaction activity."],
assets: ["Assets", "View CLC, tokens, and balances."],
nfts: ["NFTs", "View and manage non-fungible token holdings."],
loans: ["Loans", "Create loans, payments, and collateral claims."],
marketing: ["Marketing", "Review campaign activity from agency wallets."],
addressbook: ["Address Book", "Manage known addresses and labels."],
settings: ["Settings", "Manage wallets, nodes, security, and app preferences."]
};
let currentTransactionType = "";
function setScopedTransactionMessage(formType, element, message, type = "") {
if (!element) {
return;
}
if (message && (currentView !== "send" || currentTransactionType !== formType)) {
return;
}
element.textContent = message;
element.classList.toggle("hidden", !message);
element.classList.toggle("error", type === "error");
element.classList.toggle("success", type === "success");
}
const navButtons = document.querySelectorAll("[data-view]");
const dashboardView = document.querySelector("#dashboard-view");
const transactionView = document.querySelector("#transaction-view");
const receiveView = document.querySelector("#receive-view");
const historyView = document.querySelector("#history-view");
const assetsView = document.querySelector("#assets-view");
const nftsView = document.querySelector("#nfts-view");
const loansView = document.querySelector("#loans-view");
const marketingView = document.querySelector("#marketing-view");
const addressbookView = document.querySelector("#addressbook-view");
const settingsView = document.querySelector("#settings-view");
const viewTitle = document.querySelector("#view-title");
const viewSubtitle = document.querySelector("#view-subtitle");
const transactionTitle = document.querySelector("#transaction-panel-title");
const transactionCopy = document.querySelector("#transaction-panel-copy");
const transactionList = document.querySelector("[data-transaction-list]");
const formPanel = document.querySelector("[data-form-panel]");
const reviewPanel = document.querySelector("[data-review-panel]");
const unbroadcastListMessage = document.querySelector("[data-unbroadcast-list-message]");
const unbroadcastReviewGrid = document.querySelector("[data-unbroadcast-review-grid]");
const unbroadcastJsonPreview = document.querySelector("[data-unbroadcast-json]");
const unbroadcastReviewMessage = document.querySelector("[data-unbroadcast-review-message]");
const unbroadcastBackButton = document.querySelector("[data-unbroadcast-back]");
const unbroadcastDeleteButton = document.querySelector("[data-unbroadcast-delete]");
const unbroadcastSignButton = document.querySelector("[data-unbroadcast-sign]");
const unbroadcastBroadcastButton = document.querySelector("[data-unbroadcast-broadcast]");
const transferAssetSelect = document.querySelector("[data-transfer-asset]");
const transferReceiverInput = document.querySelector("[data-transfer-receiver]");
const transferAmountInput = document.querySelector("[data-transfer-amount]");
const transferFeeInput = document.querySelector("[data-transfer-fee]");
const transferCoin = document.querySelector("[data-transfer-coin]");
const transferNftSeries = document.querySelector("[data-transfer-nft-series]");
const transferBalance = document.querySelector("[data-transfer-balance]");
const transferSender = document.querySelector("[data-transfer-sender]");
const transferMessage = document.querySelector("[data-transfer-message]");
const transferActions = document.querySelector("[data-transfer-actions]");
const marketingCampaignForm = document.querySelector("[data-marketing-campaign-form]");
const marketingCampaignAdvertiserInput = document.querySelector("[data-marketing-campaign-advertiser]");
const marketingCampaignIdInput = document.querySelector("[data-marketing-campaign-id]");
const marketingCampaignLoadButton = document.querySelector("[data-marketing-campaign-load]");
const marketingCampaignRefreshButton = document.querySelector("[data-marketing-campaign-refresh]");
const marketingCampaignRows = document.querySelector("[data-marketing-campaign-rows]");
const marketingCampaignCards = document.querySelector("[data-marketing-campaign-cards]");
const marketingCampaignState = document.querySelector("[data-marketing-campaign-state]");
const marketingCampaignCount = document.querySelector("[data-marketing-campaign-count]");
const marketingCampaignPagination = document.querySelector("[data-marketing-campaign-pagination]");
const marketingCampaignPageInput = document.querySelector("[data-marketing-campaign-page-input]");
const marketingCampaignPageTotal = document.querySelector("[data-marketing-campaign-page-total]");
const marketingTotalRecords = document.querySelector("[data-marketing-total-records]");
const marketingTotalImpressions = document.querySelector("[data-marketing-total-impressions]");
const marketingTotalClicks = document.querySelector("[data-marketing-total-clicks]");
const marketingAvgImpressionValue = document.querySelector("[data-marketing-avg-impression-value]");
const marketingAvgClickValue = document.querySelector("[data-marketing-avg-click-value]");
const marketingTotalValue = document.querySelector("[data-marketing-total-value]");
const createTokenForm = document.querySelector("[data-create-token-form]");
const createTokenActions = document.querySelector("[data-create-token-actions]");
const createTokenTickerInput = document.querySelector("[data-create-token-ticker]");
const createTokenNumberInput = document.querySelector("[data-create-token-number]");
const createTokenFeeInput = document.querySelector("[data-create-token-fee]");
const createTokenAllowMoreInput = document.querySelector("[data-create-token-allow-more]");
const createTokenCap = document.querySelector("[data-create-token-cap]");
const createTokenCreator = document.querySelector("[data-create-token-creator]");
const createTokenFeeLabel = document.querySelector("[data-create-token-fee-label]");
const createTokenMessage = document.querySelector("[data-create-token-message]");
const issueTokenForm = document.querySelector("[data-issue-token-form]");
const issueTokenActions = document.querySelector("[data-issue-token-actions]");
const issueTokenTickerSelect = document.querySelector("[data-issue-token-ticker]");
const issueTokenNumberInput = document.querySelector("[data-issue-token-number]");
const issueTokenFeeInput = document.querySelector("[data-issue-token-fee]");
const issueTokenCreator = document.querySelector("[data-issue-token-creator]");
const issueTokenPolicy = document.querySelector("[data-issue-token-policy]");
const issueTokenFeeLabel = document.querySelector("[data-issue-token-fee-label]");
const issueTokenMessage = document.querySelector("[data-issue-token-message]");
const createNftForm = document.querySelector("[data-create-nft-form]");
const createNftActions = document.querySelector("[data-create-nft-actions]");
const createNftSeriesSelect = document.querySelector("[data-create-nft-series]");
const createNftNameInput = document.querySelector("[data-create-nft-name]");
const createNftIpfsInput = document.querySelector("[data-create-nft-ipfs]");
const createNftCountInput = document.querySelector("[data-create-nft-count]");
const createNftDescriptionInput = document.querySelector("[data-create-nft-description]");
const createNftFeeInput = document.querySelector("[data-create-nft-fee]");
const createNftCreator = document.querySelector("[data-create-nft-creator]");
const createNftTypeLabel = document.querySelector("[data-create-nft-type-label]");
const createNftCountLabel = document.querySelector("[data-create-nft-count-label]");
const createNftFeeLabel = document.querySelector("[data-create-nft-fee-label]");
const createNftMessage = document.querySelector("[data-create-nft-message]");
const swapOfferForm = document.querySelector("[data-swap-offer-form]");
const swapOfferActions = document.querySelector("[data-swap-offer-actions]");
const swapOfferAssetSelect = document.querySelector("[data-swap-offer-asset]");
const swapOfferAmountInput = document.querySelector("[data-swap-offer-amount]");
const swapOfferTipInput = document.querySelector("[data-swap-offer-tip]");
const swapRequestAssetSelect = document.querySelector("[data-swap-request-asset]");
const swapRequestAmountInput = document.querySelector("[data-swap-request-amount]");
const swapRequestTipInput = document.querySelector("[data-swap-request-tip]");
const swapCounterpartyInput = document.querySelector("[data-swap-counterparty]");
const swapExpirationInput = document.querySelector("[data-swap-expiration]");
const swapSender = document.querySelector("[data-swap-sender]");
const swapAvailable = document.querySelector("[data-swap-available]");
const swapFees = document.querySelector("[data-swap-fees]");
const swapSavedPathPanel = document.querySelector("[data-swap-saved-path-panel]");
const swapSavedPathInput = document.querySelector("[data-swap-saved-path]");
const swapCopyPathButton = document.querySelector("[data-swap-copy-path]");
const swapOfferMessage = document.querySelector("[data-swap-offer-message]");
const loanOfferForm = document.querySelector("[data-loan-offer-form]");
const loanOfferActions = document.querySelector("[data-loan-offer-actions]");
const loanAssetSelect = document.querySelector("[data-loan-asset]");
const loanAmountInput = document.querySelector("[data-loan-amount]");
const loanBorrowerInput = document.querySelector("[data-loan-borrower]");
const loanStartInput = document.querySelector("[data-loan-start]");
const loanCollateralSelect = document.querySelector("[data-loan-collateral]");
const loanCollateralAmountInput = document.querySelector("[data-loan-collateral-amount]");
const loanPaymentPeriodSelect = document.querySelector("[data-loan-payment-period]");
const loanPaymentNumberInput = document.querySelector("[data-loan-payment-number]");
const loanPaymentAmountInput = document.querySelector("[data-loan-payment-amount]");
const loanGracePeriodInput = document.querySelector("[data-loan-grace-period]");
const loanMaxLateValueInput = document.querySelector("[data-loan-max-late-value]");
const loanFeeInput = document.querySelector("[data-loan-fee]");
const loanLender = document.querySelector("[data-loan-lender]");
const loanAvailable = document.querySelector("[data-loan-available]");
const loanTotalRepayment = document.querySelector("[data-loan-total-repayment]");
const loanSavedPathPanel = document.querySelector("[data-loan-saved-path-panel]");
const loanSavedPathInput = document.querySelector("[data-loan-saved-path]");
const loanCopyPathButton = document.querySelector("[data-loan-copy-path]");
const loanOfferMessage = document.querySelector("[data-loan-offer-message]");
const loanPaymentForm = document.querySelector("[data-loan-payment-form]");
const loanPaymentActions = document.querySelector("[data-loan-payment-actions]");
const loanPaymentContractSelect = document.querySelector("[data-loan-payment-contract]");
const loanPaymentValueInput = document.querySelector("[data-loan-payment-value]");
const loanPaymentTipInput = document.querySelector("[data-loan-payment-tip]");
const loanPaymentFeeInput = document.querySelector("[data-loan-payment-fee]");
const loanPaymentLender = document.querySelector("[data-loan-payment-lender]");
const loanPaymentAsset = document.querySelector("[data-loan-payment-asset]");
const loanPaymentScheduled = document.querySelector("[data-loan-payment-scheduled]");
const loanPaymentConfirmed = document.querySelector("[data-loan-payment-confirmed]");
const loanPaymentPending = document.querySelector("[data-loan-payment-pending]");
const loanPaymentRemaining = document.querySelector("[data-loan-payment-remaining]");
const loanPaymentWarning = document.querySelector("[data-loan-payment-warning]");
const loanPaymentMessage = document.querySelector("[data-loan-payment-message]");
const collateralClaimForm = document.querySelector("[data-collateral-claim-form]");
const collateralClaimActions = document.querySelector("[data-collateral-claim-actions]");
const collateralClaimContractSelect = document.querySelector("[data-collateral-claim-contract]");
const collateralClaimFeeInput = document.querySelector("[data-collateral-claim-fee]");
const collateralClaimRole = document.querySelector("[data-collateral-claim-role]");
const collateralClaimReason = document.querySelector("[data-collateral-claim-reason]");
const collateralClaimLender = document.querySelector("[data-collateral-claim-lender]");
const collateralClaimBorrower = document.querySelector("[data-collateral-claim-borrower]");
const collateralClaimLoan = document.querySelector("[data-collateral-claim-loan]");
const collateralClaimCollateral = document.querySelector("[data-collateral-claim-collateral]");
const collateralClaimConfirmed = document.querySelector("[data-collateral-claim-confirmed]");
const collateralClaimRemaining = document.querySelector("[data-collateral-claim-remaining]");
const collateralClaimMessage = document.querySelector("[data-collateral-claim-message]");
const burnForm = document.querySelector("[data-burn-form]");
const burnActions = document.querySelector("[data-burn-actions]");
const burnAssetSelect = document.querySelector("[data-burn-asset]");
const burnAmountInput = document.querySelector("[data-burn-amount]");
const burnFeeInput = document.querySelector("[data-burn-fee]");
const burnAddress = document.querySelector("[data-burn-address]");
const burnAvailable = document.querySelector("[data-burn-available]");
const burnType = document.querySelector("[data-burn-type]");
const burnFeeLabel = document.querySelector("[data-burn-fee-label]");
const burnMessage = document.querySelector("[data-burn-message]");
const marketingForm = document.querySelector("[data-marketing-form]");
const marketingActions = document.querySelector("[data-marketing-actions]");
const marketingCampaignInput = document.querySelector("[data-marketing-campaign]");
const marketingAdTypeSelect = document.querySelector("[data-marketing-ad-type]");
const marketingKeywordInput = document.querySelector("[data-marketing-keyword]");
const marketingDisplayedInput = document.querySelector("[data-marketing-displayed]");
const marketingImpressionInput = document.querySelector("[data-marketing-impression]");
const marketingClickInput = document.querySelector("[data-marketing-click]");
const marketingImpressionValueInput = document.querySelector("[data-marketing-impression-value]");
const marketingClickValueInput = document.querySelector("[data-marketing-click-value]");
const marketingFeeInput = document.querySelector("[data-marketing-fee]");
const marketingAdvertiser = document.querySelector("[data-marketing-advertiser]");
const marketingFeeLabel = document.querySelector("[data-marketing-fee-label]");
const marketingMessage = document.querySelector("[data-marketing-message]");
const vanityForm = document.querySelector("[data-vanity-form]");
const vanityActions = document.querySelector("[data-vanity-actions]");
const vanityNameInput = document.querySelector("[data-vanity-name]");
const vanityFeeInput = document.querySelector("[data-vanity-fee]");
const vanityOwner = document.querySelector("[data-vanity-owner]");
const vanityPreview = document.querySelector("[data-vanity-preview]");
const vanityFeeLabel = document.querySelector("[data-vanity-fee-label]");
const vanityMessage = document.querySelector("[data-vanity-message]");
const bridgeStatus = document.querySelector("[data-bridge-status]");
const createWalletForm = document.querySelector("[data-create-wallet-form]");
const loadWalletForm = document.querySelector("[data-load-wallet-form]");
const importKeyForm = document.querySelector("[data-import-key-form]");
const importImageForm = document.querySelector("[data-import-image-form]");
const walletNameInput = document.querySelector("[data-wallet-name]");
const walletKeyInput = document.querySelector("[data-wallet-key]");
const walletKeyConfirmInput = document.querySelector("[data-wallet-key-confirm]");
const createWalletSubmit = document.querySelector("[data-create-wallet-submit]");
const startupMessage = document.querySelector("[data-startup-message]");
const walletListSelect = document.querySelector("[data-wallet-list]");
const loadWalletKeyInput = document.querySelector("[data-load-wallet-key]");
const loadWalletSubmit = document.querySelector("[data-load-wallet-submit]");
const loadMessage = document.querySelector("[data-load-message]");
const importWalletNameInput = document.querySelector("[data-import-wallet-name]");
const importPrivateKeyInput = document.querySelector("[data-import-private-key]");
const importWalletKeyInput = document.querySelector("[data-import-wallet-key]");
const importWalletKeyConfirmInput = document.querySelector("[data-import-wallet-key-confirm]");
const importKeySubmit = document.querySelector("[data-import-key-submit]");
const importMessage = document.querySelector("[data-import-message]");
const importImageWalletNameInput = document.querySelector("[data-import-image-wallet-name]");
const importImagePathInput = document.querySelector("[data-import-image-path]");
const importImageWalletKeyInput = document.querySelector("[data-import-image-wallet-key]");
const importImageWalletKeyConfirmInput = document.querySelector("[data-import-image-wallet-key-confirm]");
const importImageSubmit = document.querySelector("[data-import-image-submit]");
const importImageMessage = document.querySelector("[data-import-image-message]");
const lockWalletButton = document.querySelector("[data-lock-wallet]");
const browseWalletButton = document.querySelector("[data-browse-wallet]");
const browsePrivateKeyImageButton = document.querySelector("[data-browse-private-key-image]");
const networkButtons = document.querySelectorAll("[data-network-toggle]");
const networkLabels = document.querySelectorAll("[data-network-label]");
const clcBalance = document.querySelector("[data-clc-balance]");
const baseBalanceLabel = document.querySelector("[data-base-balance-label]");
const balanceStatus = document.querySelector("[data-balance-status]");
const tokenBalances = document.querySelector("[data-token-balances]");
const broadcastNodeButton = document.querySelector("[data-broadcast-node]");
const registrationBadge = document.querySelector(".registration-badge");
const registrationAction = document.querySelector("[data-registration-action]");
const registrationNotice = document.querySelector("[data-registration-notice]");
const recentTransactionsBody = document.querySelector("[data-recent-transactions]");
const recentTransactionsState = document.querySelector("[data-recent-transactions-state]");
const historyTransactionsBody = document.querySelector("[data-history-transactions]");
const historyCards = document.querySelector("[data-history-cards]");
const historyState = document.querySelector("[data-history-state]");
const historyCount = document.querySelector("[data-history-count]");
const historyPagination = document.querySelector("[data-history-pagination]");
const historyPageInput = document.querySelector("[data-history-page-input]");
const historyPageTotal = document.querySelector("[data-history-page-total]");
const historySearch = document.querySelector("[data-history-search]");
const historyExportOpen = document.querySelector("[data-history-export-open]");
const historyExportModal = document.querySelector("[data-history-export-modal]");
const historyExportClose = document.querySelector("[data-history-export-close]");
const historyExportMessage = document.querySelector("[data-history-export-message]");
const historyExportFormatButtons = document.querySelectorAll("[data-history-export-format]");
const assetsSearch = document.querySelector("[data-assets-search]");
const assetsSort = document.querySelector("[data-assets-sort]");
const assetsFilterButtons = document.querySelectorAll("[data-assets-filter]");
const assetsCards = document.querySelector("[data-assets-cards]");
const assetsState = document.querySelector("[data-assets-state]");
const loansSearch = document.querySelector("[data-loans-search]");
const loansRoleFilter = document.querySelector("[data-loans-role-filter]");
const loansStatusButtons = document.querySelectorAll("[data-loans-status-filter]");
const loansRefreshButton = document.querySelector("[data-loans-refresh]");
const loansCreateButton = document.querySelector("[data-loans-create]");
const loansList = document.querySelector("[data-loans-list]");
const loansState = document.querySelector("[data-loans-state]");
const loanDetailGrid = document.querySelector("[data-loan-detail-grid]");
const loanPaymentHistory = document.querySelector("[data-loan-payment-history]");
const loanCopyContractButton = document.querySelector("[data-loan-copy-contract]");
const loanMakePaymentButton = document.querySelector("[data-loan-make-payment]");
const loanClaimCollateralButton = document.querySelector("[data-loan-claim-collateral]");
const networkInfoHeight = document.querySelector("[data-network-info-height]");
const networkInfoNetwork = document.querySelector("[data-network-info-network]");
const networkInfoDifficulty = document.querySelector("[data-network-info-difficulty]");
const networkInfoMempool = document.querySelector("[data-network-info-mempool]");
const networkInfoChainTx = document.querySelector("[data-network-info-chain-tx]");
const nodeInfoNode = document.querySelector("[data-node-info-node]");
const nodeInfoHeight = document.querySelector("[data-node-info-height]");
const nodeInfoTime = document.querySelector("[data-node-info-time]");
const nodeInfoLastChecked = document.querySelector("[data-node-info-last-checked]");
const nodeInfoLargestFee = document.querySelector("[data-node-info-largest-fee]");
const paymentAssetSelect = document.querySelector("[data-payment-asset]");
const paymentAmountInput = document.querySelector("[data-payment-amount]");
const paymentLabelInput = document.querySelector("[data-payment-label]");
const paymentUriInput = document.querySelector("[data-payment-uri]");
const topbarPaymentUriForm = document.querySelector("[data-payment-uri-entry]");
const topbarPaymentUriInput = document.querySelector("[data-topbar-payment-uri]");
const paymentQrCanvas = document.querySelector("[data-payment-qr]");
const paymentQrLabel = document.querySelector("[data-payment-qr-label]");
const copyPaymentQrButton = document.querySelector("[data-copy-payment-qr]");
const copyPaymentUriButton = document.querySelector("[data-copy-payment-uri]");
const paymentMessage = document.querySelector("[data-payment-message]");
const receiveRegistration = document.querySelector("[data-receive-registration]");
const addressBookRows = document.querySelector("[data-address-book-rows]");
const addressBookCards = document.querySelector("[data-address-book-cards]");
const addressBookSearch = document.querySelector("[data-address-book-search]");
const addressBookSort = document.querySelector("[data-address-book-sort]");
const addressBookState = document.querySelector("[data-address-book-state]");
const addressBookRefreshVanity = document.querySelector("[data-address-book-refresh-vanity]");
const settingsSaveButton = document.querySelector("[data-settings-save]");
const settingsMessage = document.querySelector("[data-settings-message]");
const settingsWalletPath = document.querySelector("[data-settings-wallet-path]");
const settingsDefaultNode = document.querySelector("[data-settings-default-node]");
const settingsSavedNodes = document.querySelector("[data-settings-saved-nodes]");
const settingsUnbroadcastPath = document.querySelector("[data-settings-unbroadcast-path]");
const settingsAddressBookPath = document.querySelector("[data-settings-address-book-path]");
const settingsIpfsGateway = document.querySelector("[data-settings-ipfs-gateway]");
const settingsAutoLockTimeout = document.querySelector("[data-settings-auto-lock-timeout]");
const settingsClipboardTimeout = document.querySelector("[data-settings-clipboard-timeout]");
const settingsRequireKey = document.querySelector("[data-settings-require-key]");
const settingsBrowseButtons = document.querySelectorAll("[data-settings-browse]");
const settingsTestNodeButton = document.querySelector("[data-settings-test-node]");
const settingsNodeTestStatus = document.querySelector("[data-settings-node-test-status]");
const settingsCacheOpenButton = document.querySelector("[data-settings-cache-open]");
const cacheClearModal = document.querySelector("[data-cache-clear-modal]");
const cacheClearCloseButton = document.querySelector("[data-cache-clear-close]");
const cacheClearMessage = document.querySelector("[data-cache-clear-message]");
const nftsList = document.querySelector("[data-nfts-list]");
const nftsState = document.querySelector("[data-nfts-state]");
const nftsRefreshButton = document.querySelector("[data-nfts-refresh]");
const nftsPagination = document.querySelector("[data-nfts-pagination]");
const nftsPageInput = document.querySelector("[data-nfts-page-input]");
const nftsPageTotal = document.querySelector("[data-nfts-page-total]");
const nftDetailTitle = document.querySelector("[data-nft-detail-title]");
const nftDetailSubtitle = document.querySelector("[data-nft-detail-subtitle]");
const nftDetailName = document.querySelector("[data-nft-detail-name]");
const nftDetailAsset = document.querySelector("[data-nft-detail-asset]");
const nftDetailCreator = document.querySelector("[data-nft-detail-creator]");
const nftDetailGenesis = document.querySelector("[data-nft-detail-genesis]");
const nftDetailMetadata = document.querySelector("[data-nft-detail-metadata]");
const nftMetadataSummary = document.querySelector("[data-nft-metadata-summary]");
const nftHistory = document.querySelector("[data-nft-history]");
let activeNetwork = "testnet";
let currentView = "dashboard";
let walletLoaded = false;
let registrationRegistered = false;
let registrationCheckInFlight = false;
let registrationRequestId = 0;
let dashboardRegisteredLoadId = 0;
let balanceRefreshTimer = null;
let balanceRefreshInFlight = false;
let balanceRefreshRequestId = 0;
let dashboardLastBalanceStatus = "";
let activeWalletBalances = [];
let activeUnbroadcastTransaction = null;
let unbroadcastRefreshInFlight = false;
let unbroadcastReviewInFlight = false;
let networkInfoRefreshTimer = null;
let nodeInfoRefreshTimer = null;
let networkInfoRefreshInFlight = false;
let nodeInfoRefreshInFlight = false;
let recentTransactionsCacheReadId = 0;
let latestTransactionsSyncId = 0;
let latestTransactionsSyncInFlight = false;
let latestDashboardTransactionsTimer = null;
let latestDashboardTxids = [];
let latestDashboardTransactionRecords = new Map();
let backgroundTransactionSyncId = 0;
let historyRefreshTimer = null;
let historyRefreshReadId = 0;
let historyRefreshInFlight = false;
let historyRefreshPending = false;
let historyCurrentPage = 1;
let historyLastRenderSignature = "";
let historySearchDebounceTimer = null;
let transactionCacheMutationChain = Promise.resolve();
let guiRuntimeSettings = {
auto_lock_timeout: "15 minutes",
clipboard_clear_timeout: "60 seconds",
require_key_before_signing: true
};
let autoLockTimer = null;
let clipboardClearTimer = null;
let lastClipboardText = "";
let activeWalletAddressHex = "";
let activeWalletAddressShort = "";
let latestKnownBlockHeight = 0;
const BALANCE_REFRESH_MS = 120000;
const NETWORK_INFO_REFRESH_MS = 60000;
const NODE_INFO_REFRESH_MS = 15000;
const DASHBOARD_RECENT_LIMIT = 5;
const BACKGROUND_HISTORY_PAGE_SIZE = 100;
const BACKGROUND_SYNC_IDLE_MS = 15000;
const DASHBOARD_LATEST_REFRESH_MS = 5000;
const DASHBOARD_INITIAL_TX_FETCH_DELAY_MS = 500;
const DASHBOARD_REFRESH_TX_FETCH_DELAY_MS = 2000;
const BACKGROUND_TX_FETCH_DELAY_MS = 2000;
const HISTORY_REFRESH_MS = 5000;
const HISTORY_PAGE_SIZE = 100;
const WALLET_RPC_RETRY_LIMIT = 3;
const WALLET_RPC_BACKOFF_BASE_MS = 1500;
const WALLET_RPC_BACKOFF_MAX_MS = 15000;
const CONFIRMED_AFTER_BLOCKS = 20;
const CHAIN_STATUS_PENDING = "pending";
const CHAIN_STATUS_FINALIZED = "finalized";
const startupTitle = document.querySelector(".startup-copy h1");
const startupDescription = document.querySelector(".startup-copy p");
const startModeButtons = document.querySelectorAll("[data-start-mode]");
const topWalletListSelect = document.querySelector("[data-top-wallet-list]");
const walletSwitchModal = document.querySelector("[data-wallet-switch-modal]");
const walletSwitchForm = document.querySelector("[data-wallet-switch-form]");
const walletSwitchName = document.querySelector("[data-wallet-switch-name]");
const walletSwitchKeyInput = document.querySelector("[data-wallet-switch-key]");
const walletSwitchSubmit = document.querySelector("[data-wallet-switch-submit]");
const walletSwitchMessage = document.querySelector("[data-wallet-switch-message]");
const walletSwitchCloseButton = document.querySelector("[data-wallet-switch-close]");
const walletSwitchCancelButton = document.querySelector("[data-wallet-switch-cancel]");
const backupKeyModal = document.querySelector("[data-backup-key-modal]");
const backupKeyForm = document.querySelector("[data-backup-key-form]");
const backupKeyTitle = document.querySelector("[data-backup-key-title]");
const backupKeyCopy = document.querySelector("[data-backup-key-copy]");
const backupKeyInput = document.querySelector("[data-backup-key-input]");
const backupKeyMessage = document.querySelector("[data-backup-key-message]");
const backupKeyCloseButton = document.querySelector("[data-backup-key-close]");
const backupKeyCancelButton = document.querySelector("[data-backup-key-cancel]");
const backupKeySubmitButton = document.querySelector("[data-backup-key-submit]");
let activeWalletPath = "";
let pendingWalletSwitch = null;
let pendingBackupKeyRequest = null;
const startupCopy = {
load: ["Open Wallet", "Select a wallet file already on this device."],
create: ["Create New Wallet", "Generate a new encrypted wallet file."],
"import-key": ["Import Private Key", "Restore from a private key value."],
"import-image": ["Import Private Key Image", "Restore from a base64 or image-based key backup."]
};
function setActiveStartMode(mode) {
startModeButtons.forEach((button) => {
button.classList.toggle("primary-start", button.dataset.startMode === mode);
});
const [title, description] = startupCopy[mode] || startupCopy.load;
if (startupTitle) {
startupTitle.textContent = title;
}
if (startupDescription) {
startupDescription.textContent = description;
}
}
function activeBaseCoin() {
return activeNetwork === "mainnet" ? "CLC" : "CLTC";
}
function shortenAddress(address) {
if (!address || address.length <= 17) {
return address || "No wallet";
}
const suffix = address.includes(".") ? address.slice(address.lastIndexOf(".") - 4) : address.slice(-8);
return `${address.slice(0, 4)}...${suffix}`;
}
function privateKeyImageSrc(privateKeyImage) {
const image = (privateKeyImage || "").trim();
if (!image) {
return "";
}
return image.startsWith("data:") ? image : `data:image/png;base64,${image}`;
}
function setWalletImageMarks(privateKeyImage) {
const imageSrc = privateKeyImageSrc(privateKeyImage);
document.querySelectorAll("[data-wallet-image-mark]").forEach((item) => {
if (!imageSrc) {
item.classList.remove("wallet-image");
item.style.backgroundImage = "";
item.textContent = "C";
return;
}
item.classList.add("wallet-image");
item.style.backgroundImage = `url("${imageSrc}")`;
item.textContent = "";
});
}
function formatAtomicBalance(value) {
const atomic = BigInt(value || "0");
const whole = atomic / 100000000n;
const fractionalRaw = atomic % 100000000n;
const fractional = fractionalRaw.toString().padStart(8, "0").replace(/0+$/, "");
return fractional ? `${whole}.${fractional}` : `${whole}`;
}
function formatNumber(value) {
return Number(value || 0).toLocaleString();
}
function formatNetworkTime(timestamp) {
const numeric = Number(timestamp || 0);
if (!numeric) {
return "--";
}
return new Date(numeric * 1000).toLocaleString();
}
function formatLargestFee(value) {
const formatted = formatAtomicBalance(value);
return `${formatted} ${activeBaseCoin()}`;
}
function networkDisplayName(info) {
const network = String(info?.network || activeNetwork || "").trim();
const prefix = String(info?.wallet_prefix || activeBaseCoin()).trim();
const label = network ? network.charAt(0).toUpperCase() + network.slice(1) : "Network";
return prefix ? `${label} / ${prefix}` : label;
}
async function populateTopWalletList() {
const invoke = window.__TAURI__?.core?.invoke;
if (!invoke || !topWalletListSelect) {
return;
}
try {
const wallets = await invoke("list_wallets", { network: activeNetwork });
topWalletListSelect.innerHTML = "";
if (!wallets.length) {
const option = document.createElement("option");
option.value = "";
option.textContent = "No wallets found";
topWalletListSelect.appendChild(option);
return;
}
wallets.forEach((wallet) => {
const option = document.createElement("option");
option.value = wallet.wallet_path;
option.textContent = wallet.wallet_name;
if (wallet.wallet_path === activeWalletPath) {
option.selected = true;
}
topWalletListSelect.appendChild(option);
});
} catch (error) {
console.error(error);
}
}
function setBroadcastNodeOptions(nodes) {
if (!broadcastNodeButton) {
return;
}
const previous = activeBroadcastNode();
const normalized = Array.isArray(nodes) ? nodes.filter((node) => node?.address) : [];
broadcastNodeButton.innerHTML = "";
if (!normalized.length) {
const option = document.createElement("option");
option.value = "";
option.textContent = "No broadcast nodes";
broadcastNodeButton.appendChild(option);
return;
}
normalized.forEach((node) => {
const option = document.createElement("option");
option.value = node.address;
option.textContent = node.address;
option.title = node.label ? `${node.label}: ${node.address}` : node.address;
broadcastNodeButton.appendChild(option);
});
const selected = normalized.some((node) => node.address === previous)
? previous
: normalized[0].address;
broadcastNodeButton.value = selected;
}
async function populateBroadcastNodeList() {
const invoke = window.__TAURI__?.core?.invoke;
if (!invoke || !broadcastNodeButton) {
return;
}
try {
const nodes = await invoke("list_broadcast_nodes");
setBroadcastNodeOptions(nodes);
} catch (error) {
console.error(error);
setBroadcastNodeOptions([]);
}
}
function assetInitial(asset) {
return (asset || "?").trim().slice(0, 1).toUpperCase() || "?";
}
function balanceAssetLabel(balance) {
if (balance.nft_series && balance.nft_series > 0) {
return `${balance.asset} #${balance.nft_series}`;
}
return balance.asset;
}
let knownNftBalanceKeys = new Set();
let knownNftBalanceNode = "";
let knownNftBalanceLoadedAt = 0;
let knownNftBalanceLoad = null;
const DASHBOARD_NFT_BALANCE_CACHE_MS = 300_000;
function balanceRegistryKey(balance) {
const asset = String(balance?.asset || "").trim().toLowerCase();
const series = Number(balance?.nft_series || 0);
return `${asset}|${series}`;
}
function isDashboardNftBalance(balance) {
if (Number(balance?.nft_series || 0) > 0) {
return true;
}
return knownNftBalanceKeys.has(balanceRegistryKey(balance));
}
function dashboardNftBalanceKeysReady() {
const broadcastNode = typeof activeBroadcastNode === "function" ? activeBroadcastNode() : "";
return (
Boolean(broadcastNode) &&
knownNftBalanceNode === broadcastNode
);
}
function dashboardNftBalanceKeysFresh() {
const broadcastNode = typeof activeBroadcastNode === "function" ? activeBroadcastNode() : "";
return (
dashboardNftBalanceKeysReady() &&
Date.now() - knownNftBalanceLoadedAt < DASHBOARD_NFT_BALANCE_CACHE_MS
);
}
function invalidateDashboardNftBalanceKeys() {
knownNftBalanceNode = "";
knownNftBalanceLoadedAt = 0;
}
async function refreshDashboardNftBalanceKeys() {
const invoke = window.__TAURI__?.core?.invoke;
const broadcastNode = typeof activeBroadcastNode === "function" ? activeBroadcastNode() : "";
if (!invoke || !broadcastNode) {
return false;
}
if (dashboardNftBalanceKeysFresh()) {
return false;
}
if (knownNftBalanceLoad) {
return knownNftBalanceLoad;
}
knownNftBalanceLoad = invoke("nft_list", { broadcastNode })
.then((rows) => {
const nextKeys = new Set();
(Array.isArray(rows) ? rows : []).forEach((nft) => {
const name = String(nft?.nft_name || "").trim().toLowerCase();
if (!name) {
return;
}
nextKeys.add(`${name}|${Number(nft?.series || 0)}`);
});
const previous = Array.from(knownNftBalanceKeys).sort().join(",");
const next = Array.from(nextKeys).sort().join(",");
knownNftBalanceKeys = nextKeys;
knownNftBalanceNode = broadcastNode;
knownNftBalanceLoadedAt = Date.now();
return previous !== next;
})
.catch(() => false)
.finally(() => {
knownNftBalanceLoad = null;
});
return knownNftBalanceLoad;
}
function setBalanceStatus(message, type = "") {
if (!balanceStatus) {
return;
}
balanceStatus.textContent = message;
balanceStatus.classList.toggle("error", type === "error");
balanceStatus.classList.toggle("success", type === "success");
}
function setBridgeStatus(message, type = "") {
if (!bridgeStatus) {
return;
}
bridgeStatus.textContent = message;
bridgeStatus.classList.toggle("error", type === "error");
bridgeStatus.classList.toggle("success", type === "success");
}
function renderBalances(balances) {
const normalized = Array.isArray(balances) ? balances : [];
activeWalletBalances = normalized;
if (typeof refreshTransferAssetOptions === "function") {
refreshTransferAssetOptions();
}
if (typeof refreshActiveTransactionFormAfterWalletData === "function") {
refreshActiveTransactionFormAfterWalletData();
}
const baseCoin = activeBaseCoin();
const baseBalance = normalized.find((balance) => {
return (balance.asset || "").trim().toUpperCase() === baseCoin && !balance.nft_series;
});
const nftKeysReady = dashboardNftBalanceKeysReady();
const tokenRows = normalized
.filter((balance) => {
const isBaseCoin = (balance.asset || "").trim().toUpperCase() === baseCoin && !balance.nft_series;
return !isBaseCoin && !isDashboardNftBalance(balance);
})
.sort((left, right) => {
const leftBalance = BigInt(left.balance || "0");
const rightBalance = BigInt(right.balance || "0");
if (rightBalance !== leftBalance) {
return rightBalance > leftBalance ? 1 : -1;
}
return balanceAssetLabel(left).localeCompare(balanceAssetLabel(right));
});
if (baseBalanceLabel) {
baseBalanceLabel.textContent = `${baseCoin} Balance`;
}
if (clcBalance) {
clcBalance.textContent = baseBalance ? `${formatAtomicBalance(baseBalance.balance)} ${baseCoin}` : `0 ${baseCoin}`;
}
if (!tokenBalances) {
return;
}
const rerenderAfterNftKeyRefresh = () => {
refreshDashboardNftBalanceKeys().then((changed) => {
if (changed && Array.isArray(activeWalletBalances)) {
renderBalances(activeWalletBalances);
}
});
};
tokenBalances.innerHTML = "";
const title = document.createElement("div");
title.className = "token-title";
title.textContent = nftKeysReady ? `Token Balances (${tokenRows.length})` : "Token Balances";
tokenBalances.appendChild(title);
if (!nftKeysReady) {
const pending = document.createElement("div");
pending.className = "empty-balance";
pending.textContent = "Loading token balances...";
tokenBalances.appendChild(pending);
rerenderAfterNftKeyRefresh();
return;
}
if (!tokenRows.length) {
const empty = document.createElement("div");
empty.className = "empty-balance";
empty.textContent = "No token balances found.";
tokenBalances.appendChild(empty);
rerenderAfterNftKeyRefresh();
return;
}
tokenRows.slice(0, 4).forEach((balance) => {
const row = document.createElement("div");
row.className = "token-row";
const mark = document.createElement("span");
mark.className = "asset-mark mini";
mark.textContent = assetInitial(balance.asset);
const asset = document.createElement("span");
asset.textContent = balanceAssetLabel(balance);
const amount = document.createElement("b");
amount.textContent = formatAtomicBalance(balance.balance);
row.append(mark, asset, amount);
tokenBalances.appendChild(row);
});
rerenderAfterNftKeyRefresh();
if (currentView === "assets" && typeof renderAssetsPage === "function") {
renderAssetsPage();
}
}
function resetTransactionForms() {
[
"resetTransferSession",
"resetCreateTokenSession",
"resetIssueTokenSession",
"resetCreateNftSession",
"resetSwapOfferSession",
"resetLoanOfferSession",
"resetLoanPaymentSession",
"resetCollateralClaimSession",
"resetBurnSession",
"resetMarketingSession",
"resetVanityAddressSession"
].forEach((name) => {
if (typeof window[name] === "function") {
window[name]();
}
});
[
"clearTransferForm",
"clearCreateTokenForm",
"clearIssueTokenForm",
"clearCreateNftForm",
"clearSwapOfferForm",
"clearLoanOfferForm",
"clearLoanPaymentForm",
"clearCollateralClaimForm",
"clearBurnForm",
"clearMarketingForm",
"clearVanityForm"
].forEach((name) => {
if (typeof window[name] === "function") {
window[name]();
}
});
[
transferMessage,
createTokenMessage,
issueTokenMessage,
createNftMessage,
swapOfferMessage,
loanOfferMessage,
loanPaymentMessage,
collateralClaimMessage,
burnMessage,
marketingMessage,
vanityMessage
].forEach((element) => {
if (!element) {
return;
}
element.textContent = "";
element.classList.add("hidden");
element.classList.remove("error", "success");
});
loanSavedPathPanel?.classList.add("hidden");
currentTransactionType = "";
}
function setRecentTransactionsState(message, mode = "") {
if (!recentTransactionsState) {
return;
}
recentTransactionsState.textContent = message;
recentTransactionsState.classList.toggle("hidden", !message);
recentTransactionsState.classList.toggle("loading", mode === "loading");
}
function clearRecentTransactionsRows() {
if (recentTransactionsBody) {
recentTransactionsBody.innerHTML = "";
}
}