900 lines
42 KiB
JavaScript
900 lines
42 KiB
JavaScript
|
|
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 = "";
|
|||
|
|
}
|
|||
|
|
}
|