// // NOTE - Change isTestMode to false prior to actual release ---- !important - You may also change identifier if you want to not show older cards. const testMode = false const minterCardIdentifierPrefix = "Minter-board-card" let isExistingCard = false let existingCardData = {} let existingCardIdentifier = {} const MIN_ADMIN_YES_VOTES = 9; const GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT = 2012800 //TODO update this to correct featureTrigger height when known, either that, or pull from core. let featureTriggerPassed = false let isApproved = false let cachedMinterAdmins let cachedMinterGroup const loadMinterBoardPage = async () => { // Clear existing content on the page const bodyChildren = document.body.children for (let i = bodyChildren.length - 1; i >= 0; i--) { const child = bodyChildren[i]; if (!child.classList.contains("menu")) { child.remove() } } // Add the "Minter Board" content const mainContent = document.createElement("div") const publishButtonColor = '#527c9d' const minterBoardNameColor = '#527c9d' mainContent.innerHTML = `
The Minter Board is where Minting Rights are Delegated.
To obtain minting rights, click 'PUBLISH CARD' and create your card. A subsequent vote will approve/deny your card.
After your card has received the necessary invite, return to the card and click the Join Group button to join the MINTER group. (A Detailed how-to guide will be coming soon.)
Refreshing cards...
" await loadCards(minterCardIdentifierPrefix) }) document.getElementById("cancel-publish-button").addEventListener("click", async () => { const cardsContainer = document.getElementById("cards-container") cardsContainer.style.display = "flex"; // Restore visibility const publishCardView = document.getElementById("publish-card-view") publishCardView.style.display = "none"; // Hide the publish form }) document.getElementById("add-link-button").addEventListener("click", async () => { const linksContainer = document.getElementById("links-container") const newLinkInput = document.createElement("input") newLinkInput.type = "text" newLinkInput.className = "card-link" newLinkInput.placeholder = "Enter QDN link" linksContainer.appendChild(newLinkInput) }) document.getElementById("publish-card-form").addEventListener("submit", async (event) => { event.preventDefault() await publishCard(minterCardIdentifierPrefix) }) document.getElementById("time-range-select").addEventListener("change", async () => { // Re-load the cards whenever user chooses a new sort option. await loadCards(minterCardIdentifierPrefix) }) document.getElementById("sort-select").addEventListener("change", async () => { // Re-load the cards whenever user chooses a new sort option. await loadCards(minterCardIdentifierPrefix) }) const showExistingCardsCheckbox = document.getElementById('show-existing-checkbox') if (showExistingCardsCheckbox) { showExistingCardsCheckbox.addEventListener('change', async (event) => { await loadCards(minterCardIdentifierPrefix) }) } //Initialize Minter Group and Admin Group await initializeCachedGroups() await featureTriggerCheck() await loadCards(minterCardIdentifierPrefix) } const initializeCachedGroups = async () => { try { const [minterGroup, minterAdmins] = await Promise.all([ fetchMinterGroupMembers(), fetchMinterGroupAdmins() ]) cachedMinterGroup = minterGroup cachedMinterAdmins = minterAdmins } catch (error) { console.error("Error initializing cached groups:", error) } } const runWithConcurrency = async (tasks, concurrency = 5) => { const results = [] let index = 0 const workers = new Array(concurrency).fill(null).map(async () => { while (index < tasks.length) { const currentIndex = index++ const task = tasks[currentIndex] results[currentIndex] = await task() } }) await Promise.all(workers) return results } const extractMinterCardsMinterName = async (cardIdentifier) => { // Ensure the identifier starts with the prefix if ((!cardIdentifier.startsWith(minterCardIdentifierPrefix)) && (!cardIdentifier.startsWith(addRemoveIdentifierPrefix))) { throw new Error('minterCard does not match identifier check') } // Split the identifier into parts const parts = cardIdentifier.split('-') // Ensure the format has at least 3 parts if (parts.length < 3) { throw new Error('Invalid identifier format') } try { if (cardIdentifier.startsWith(minterCardIdentifierPrefix)){ const searchSimpleResults = await searchSimple('BLOG_POST', `${cardIdentifier}`, '', 1, 0, '', false, true) const minterName = await searchSimpleResults.name return minterName } else if (cardIdentifier.startsWith(addRemoveIdentifierPrefix)) { const searchSimpleResults = await searchSimple('BLOG_POST', `${cardIdentifier}`, '', 1, 0, '', false, true) const publisherName = searchSimpleResults.name const cardDataResponse = await qortalRequest({ action: "FETCH_QDN_RESOURCE", name: publisherName, service: "BLOG_POST", identifier: cardIdentifier, }) let nameInvalid = false const minterName = cardDataResponse.minterName if (minterName){ return minterName } else { nameInvalid = true console.warn(`fuckery detected on identifier: ${cardIdentifier}, hello dipshit Mythril!, name invalid? Name doesn't match publisher? Returning invalid flag + publisherName...`) return publisherName } } } catch (error) { throw error } } const groupAndLabelByIdentifier = (allCards) => { // Group by identifier const mapById = new Map() allCards.forEach(card => { if (!mapById.has(card.identifier)) { mapById.set(card.identifier, []) } mapById.get(card.identifier).push(card) }) // For each identifier's group, sort oldest->newest so the first is "master" const output = [] for (const [identifier, group] of mapById.entries()) { group.sort((a, b) => { const aTime = a.created || 0 const bTime = b.created || 0 return aTime - bTime // oldest first }) // Mark the first as master group[0].isMaster = true // The rest are updates for (let i = 1; i < group.length; i++) { group[i].isMaster = false } // push them all to output output.push(...group) } return output } const groupByIdentifierOldestFirst = (allCards) => { // map of identifier => array of cards const mapById = new Map() allCards.forEach(card => { if (!mapById.has(card.identifier)) { mapById.set(card.identifier, []) } mapById.get(card.identifier).push(card) }) // sort each group oldest->newest for (const [identifier, group] of mapById.entries()) { group.sort((a, b) => { const aTime = a.created || 0 const bTime = b.created || 0 return aTime - bTime // oldest first }) } return mapById } const buildMinterNameGroups = async (mapById) => { // We'll build an array of objects: { minterName, cards } // Then we can combine any that share the same minterName. const nameGroups = [] for (let [identifier, group] of mapById.entries()) { // group[0] is the oldest => "master" card let masterCard = group[0] // Filter out any cards that are not published by the 'masterPublisher' const masterPublisherName = masterCard.name // Remove any cards in this identifier group that have a different publisherName const filteredGroup = group.filter(c => c.name === masterPublisherName) // If filtering left zero cards, skip entire group if (!filteredGroup.length) { console.warn(`All cards removed for identifier=${identifier} (different publishers). Skipping.`) continue } // Reassign group to the filtered version, then re-define masterCard group = filteredGroup masterCard = group[0] // oldest after filtering // attempt to obtain minterName from the master card let masterMinterName try { masterMinterName = await extractMinterCardsMinterName(masterCard.identifier) } catch (err) { console.warn(`Skipping entire group ${identifier}, no valid minterName from master`, err) continue } // Store an object with the minterName we extracted, plus all cards in that group nameGroups.push({ minterName: masterMinterName, cards: group // includes the master & updates }) } // Combine them: minterName => array of *all* cards from all matching groups const combinedMap = new Map() for (const entry of nameGroups) { const mName = entry.minterName if (!combinedMap.has(mName)) { combinedMap.set(mName, []) } combinedMap.get(mName).push(...entry.cards) } return combinedMap } const getNewestCardPerMinterName = (combinedMap) => { // We'll produce an array of the newest card for each minterName, this will be utilized as the 'final filter' to display cards published/updated by unique minters. const finalOutput = [] for (const [mName, cardArray] of combinedMap.entries()) { // sort by updated or created, descending => newest first cardArray.sort((a, b) => { const aTime = a.updated || a.created || 0 const bTime = b.updated || b.created || 0 return bTime - aTime }) // newest is [0] finalOutput.push(cardArray[0]) } // Then maybe globally sort them newest first finalOutput.sort((a, b) => { const aTime = a.updated || a.created || 0 const bTime = b.updated || b.created || 0 return bTime - aTime }) return finalOutput } const processMinterBoardCards = async (allValidCards) => { // group by identifier, sorted oldest->newest const mapById = groupByIdentifierOldestFirst(allValidCards) // build a map of minterName => all cards from those identifiers const minterNameMap = await buildMinterNameGroups(mapById) // from that map, keep only the single newest card per minterName const newestCards = getNewestCardPerMinterName(minterNameMap) // return final array of all newest cards return newestCards } const processARBoardCards = async (allValidCards) => { const mapById = groupByIdentifierOldestFirst(allValidCards) // build a map of minterName => all cards from those identifiers const mapByName = await buildMinterNameGroups(mapById) // For each minterName group, we might want to sort them newest->oldest const finalOutput = [] for (const [minterName, group] of mapByName.entries()) { group.sort((a, b) => { const aTime = a.updated || a.created || 0 const bTime = b.updated || b.created || 0 return bTime - aTime }) // both resolution for the duplicate QuickMythril card, and handling of all future duplicates that may be published... if (group[0].identifier === 'QM-AR-card-Xw3dxL') { console.warn(`This is a bug that allowed a duplicate prior to the logic displaying them based on original publisher only... displaying in reverse order...`) group[0].isDuplicate = true for (let i = 1; i < group.length; i++) { group[i].isDuplicate = false } }else { group[0].isDuplicate = false for (let i = 1; i < group.length; i++) { group[i].isDuplicate = true } } // push them all finalOutput.push(...group) } // Sort final by newest overall finalOutput.sort((a, b) => { const aTime = a.updated || a.created || 0 const bTime = b.updated || b.created || 0 return bTime - aTime }) return finalOutput } //Main function to load the Minter Cards ---------------------------------------- const loadCards = async (cardIdentifierPrefix) => { if ((!cachedMinterGroup || cachedMinterGroup.length === 0) || (!cachedMinterAdmins || cachedMinterAdmins.length === 0)) { await initializeCachedGroups() } const cardsContainer = document.getElementById("cards-container") cardsContainer.innerHTML = "Loading cards...
" const counterSpan = document.getElementById("board-card-counter") if (counterSpan) counterSpan.textContent = "(loading...)" const isARBoard = cardIdentifierPrefix.startsWith("QM-AR-card") const showExistingCheckbox = document.getElementById("show-existing-checkbox") const showExisting = showExistingCheckbox && showExistingCheckbox.checked let afterTime = 0 const timeRangeSelect = document.getElementById("time-range-select") if (timeRangeSelect) { const days = parseInt(timeRangeSelect.value, 10) if (days > 0) { const now = Date.now() afterTime = now - days * 24 * 60 * 60 * 1000 } } try { const rawResults = await searchSimple('BLOG_POST', cardIdentifierPrefix, '', 0, 0, '', false, true, afterTime) if (!rawResults || !Array.isArray(rawResults) || rawResults.length === 0) { cardsContainer.innerHTML = "No cards found.
" return } const validated = (await Promise.all( rawResults.map(async (r) => (await validateCardStructure(r)) ? r : null) )).filter(Boolean) if (validated.length === 0) { cardsContainer.innerHTML = "No valid cards found.
" return } let processedCards if (isARBoard) { processedCards = await processARBoardCards(validated) } else { processedCards = await processMinterBoardCards(validated) } let selectedSort = "newest" const sortSelect = document.getElementById("sort-select") if (sortSelect) { selectedSort = sortSelect.value } if (selectedSort === "name") { processedCards.sort((a, b) => (a.name||"").localeCompare(b.name||"")) } else if (selectedSort === 'recent-comments') { // If you need the newest comment timestamp for (let card of finalCards) { card.newestCommentTimestamp = await getNewestCommentTimestamp(card.identifier) } finalCards.sort((a, b) => (b.newestCommentTimestamp || 0) - (a.newestCommentTimestamp || 0) ) } else if (selectedSort === 'least-votes') { await applyVoteSortingData(finalCards, /* ascending= */ true) } else if (selectedSort === 'most-votes') { await applyVoteSortingData(finalCards, /* ascending= */ false) } cardsContainer.innerHTML = "" // reset for (const card of processedCards) { const skeletonHTML = createSkeletonCardHTML(card.identifier) cardsContainer.insertAdjacentHTML("beforeend", skeletonHTML) } const finalCardsArray = [] const alreadyMinterCards = [] const tasks = processedCards.map(card => { return async () => { // We'll store an object with skip info, QDN data, etc. const result = { card, skip: false, skipReason: "", isAlreadyMinter: false, cardData: null, } try { const data = await qortalRequest({ action: "FETCH_QDN_RESOURCE", name: card.name, service: "BLOG_POST", identifier: card.identifier }) if (!data || !data.poll) { result.skip = true result.skipReason = "Missing or invalid poll" return result } const pollPublisherAddress = await getPollOwnerAddressCached(data.poll) const cardPublisherAddress = await fetchOwnerAddressFromNameCached(card.name) if (pollPublisherAddress !== cardPublisherAddress) { result.skip = true result.skipReason = "Poll hijack mismatch" return result } // ARBoard => verify user is minter/admin if (isARBoard) { const ok = await verifyMinterCached(data.minterName) if (!ok) { result.skip = true result.skipReason = "Card user not minter => skip from ARBoard" return result } } else { // MinterBoard => skip if user is minter const isAlready = await verifyMinterCached(data.creator) if (isAlready) { result.skip = true result.skipReason = "Already a minter" result.isAlreadyMinter = true result.cardData = data return result } } // If we get here => it's a keeper result.cardData = data } catch (err) { console.warn("Error fetching resource or skip logic:", err) result.skip = true result.skipReason = "Error: " + err } return result } }) // ADJUST THE CONCURRENCY TO INCREASE THE AMOUNT OF CARDS PROCESSED AT ONCE. INCREASE UNTIL THERE ARE ISSUES. const concurrency = 30 const results = await runWithConcurrency(tasks, concurrency) // Fill final arrays for (const r of results) { if (r.skip && r.isAlreadyMinter) { alreadyMinterCards.push({ ...r.card, cardDataResponse: r.cardData }) removeSkeleton(r.card.identifier) } else if (r.skip) { console.warn(`Skipping card ${r.card.identifier}, reason=${r.skipReason}`) removeSkeleton(r.card.identifier) } else { // keeper finalCardsArray.push({ ...r.card, cardDataResponse: r.cardData }) } } for (const cardObj of finalCardsArray) { try { const pollResults = await fetchPollResultsCached(cardObj.cardDataResponse.poll) const commentCount = await countCommentsCached(cardObj.identifier) const cardUpdatedTime = cardObj.updated || cardObj.created || null const bgColor = generateDarkPastelBackgroundBy(cardObj.name) // If ARBoard => createARCardHTML else createCardHTML const finalCardHTML = isARBoard ? await createARCardHTML( cardObj.cardDataResponse, pollResults, cardObj.identifier, commentCount, cardUpdatedTime, bgColor, await fetchOwnerAddressFromNameCached(cardObj.name), cardObj.isDuplicate ) : await createCardHTML( cardObj.cardDataResponse, pollResults, cardObj.identifier, commentCount, cardUpdatedTime, bgColor, await fetchOwnerAddressFromNameCached(cardObj.name) ) replaceSkeleton(cardObj.identifier, finalCardHTML) } catch (err) { console.error(`Error finalizing card ${cardObj.identifier}:`, err) removeSkeleton(cardObj.identifier) } } if (showExisting && alreadyMinterCards.length > 0) { console.log(`Rendering minted cards because showExisting is checked, count=${alreadyMinterCards.length}`) for (const minted of alreadyMinterCards) { const skeletonHTML = createSkeletonCardHTML(minted.identifier) cardsContainer.insertAdjacentHTML("beforeend", skeletonHTML) try { const pollResults = await fetchPollResultsCached(minted.cardDataResponse.poll) const commentCount = await countCommentsCached(minted.identifier) const cardUpdatedTime = minted.updated || minted.created || null const bgColor = generateDarkPastelBackgroundBy(minted.name) const finalCardHTML = await createCardHTML( minted.cardDataResponse, pollResults, minted.identifier, commentCount, cardUpdatedTime, bgColor, await fetchOwnerAddressFromNameCached(minted.name), /* isExistingMinter= */ true ) replaceSkeleton(minted.identifier, finalCardHTML) } catch (err) { console.error(`Error finalizing minted card ${minted.identifier}:`, err) removeSkeleton(minted.identifier) } } } if (counterSpan) { const displayed = finalCardsArray.length const minted = alreadyMinterCards.length counterSpan.textContent = `(${displayed} displayed, ${minted} minters)` } } catch (error) { console.error("Error loading cards:", error) cardsContainer.innerHTML = "Failed to load cards.
" if (counterSpan) { counterSpan.textContent = "(error loading)" } } } const verifyMinterCache = new Map() const verifyMinterCached = async (nameOrAddress) => { if (verifyMinterCache.has(nameOrAddress)) { return verifyMinterCache.get(nameOrAddress) } const result = await verifyMinter(nameOrAddress) verifyMinterCache.set(nameOrAddress, result) return result } const verifyMinter = async (minterName) => { try { const nameInfo = await getNameInfoCached(minterName) if (!nameInfo) return false const minterAddress = nameInfo.owner const isValid = await getAddressInfo(minterAddress) if (!isValid) return false // Then check if they're in the minter group // const minterGroup = await fetchMinterGroupMembers() const minterGroup = cachedMinterGroup // const adminGroup = await fetchMinterGroupAdmins() const adminGroup = cachedMinterAdmins const minterGroupAddresses = minterGroup.map(m => m.member) const adminGroupAddresses = adminGroup.map(m => m.member) return (minterGroupAddresses.includes(minterAddress) || adminGroupAddresses.includes(minterAddress)) } catch (err) { console.warn("verifyMinter error:", err) return false } } const applyVoteSortingData = async (cards, ascending = true) => { // const minterGroupMembers = await fetchMinterGroupMembers() const minterGroupMembers = cachedMinterGroup // const minterAdmins = await fetchMinterGroupAdmins() const minterAdmins = cachedMinterAdmins for (const card of cards) { try { const cardDataResponse = await qortalRequest({ action: "FETCH_QDN_RESOURCE", name: card.name, service: "BLOG_POST", identifier: card.identifier, }) if (!cardDataResponse || !cardDataResponse.poll) { card._adminVotes = 0 card._adminYes = 0 card._minterVotes = 0 card._minterYes = 0 continue } const pollResults = await fetchPollResultsCached(cardDataResponse.poll); const { adminYes, adminNo, minterYes, minterNo } = await processPollData( pollResults, minterGroupMembers, minterAdmins, cardDataResponse.creator, card.identifier ) card._adminVotes = adminYes + adminNo card._adminYes = adminYes card._minterVotes = minterYes + minterNo card._minterYes = minterYes } catch (error) { console.warn(`Error fetching or processing poll for card ${card.identifier}:`, error) card._adminVotes = 0 card._adminYes = 0 card._minterVotes = 0 card._minterYes = 0 } } if (ascending) { // least votes first cards.sort((a, b) => { const diffAdminTotal = a._adminVotes - b._adminVotes if (diffAdminTotal !== 0) return diffAdminTotal const diffAdminYes = a._adminYes - b._adminYes if (diffAdminYes !== 0) return diffAdminYes const diffMinterTotal = a._minterVotes - b._minterVotes if (diffMinterTotal !== 0) return diffMinterTotal return a._minterYes - b._minterYes }) } else { // most votes first cards.sort((a, b) => { const diffAdminTotal = b._adminVotes - a._adminVotes if (diffAdminTotal !== 0) return diffAdminTotal const diffAdminYes = b._adminYes - a._adminYes if (diffAdminYes !== 0) return diffAdminYes const diffMinterTotal = b._minterVotes - a._minterVotes if (diffMinterTotal !== 0) return diffMinterTotal return b._minterYes - a._minterYes }) } } const removeSkeleton = (cardIdentifier) => { const skeletonCard = document.getElementById(`skeleton-${cardIdentifier}`) if (skeletonCard) { skeletonCard.remove() } } const replaceSkeleton = (cardIdentifier, htmlContent) => { const skeletonCard = document.getElementById(`skeleton-${cardIdentifier}`) if (skeletonCard) { skeletonCard.outerHTML = htmlContent } } const createSkeletonCardHTML = (cardIdentifier) => { return `LOADING CARD...
PLEASE BE PATIENT
While data loads from QDN...
Poll data is invalid or missing.
`, userVote: null } } const memberAddresses = minterGroupMembers.map(m => m.member) const minterAdminAddresses = minterAdmins.map(m => m.member) const adminGroupsMembers = await fetchAllAdminGroupsMembers() const featureTriggerPassed = await featureTriggerCheck() const groupAdminAddresses = adminGroupsMembers.map(m => m.member) let adminAddresses = [...minterAdminAddresses] if (!featureTriggerPassed) { console.log(`featureTrigger is NOT passed, only showing admin results from Minter Admins and Group Admins`) adminAddresses = [...minterAdminAddresses, ...groupAdminAddresses] } let adminYes = 0, adminNo = 0 let minterYes = 0, minterNo = 0 let yesWeight = 0, noWeight = 0 let userVote = null for (const w of pollData.voteWeights) { if (w.optionName.toLowerCase() === 'yes') { yesWeight = w.voteWeight } else if (w.optionName.toLowerCase() === 'no') { noWeight = w.voteWeight } } const voterPromises = pollData.votes.map(async (vote) => { const optionIndex = vote.optionIndex; // 0 => yes, 1 => no const voterPublicKey = vote.voterPublicKey const voterAddress = await getAddressFromPublicKey(voterPublicKey) if (voterAddress === userState.accountAddress) { userVote = optionIndex } if (optionIndex === 0) { if (adminAddresses.includes(voterAddress)) { adminYes++ } else if (memberAddresses.includes(voterAddress)) { minterYes++ } else { console.log(`voter ${voterAddress} is not a minter nor an admin... Not included in aggregates.`) } } else if (optionIndex === 1) { if (adminAddresses.includes(voterAddress)) { adminNo++ } else if (memberAddresses.includes(voterAddress)) { minterNo++ } else { console.log(`voter ${voterAddress} is not a minter nor an admin... Not included in aggregates.`) } } let voterName = '' try { const nameInfo = await getNameFromAddress(voterAddress) if (nameInfo) { voterName = nameInfo if (nameInfo === voterAddress) voterName = '' } } catch (err) { console.warn(`No name for address ${voterAddress}`, err) } let blocksMinted = 0 try { const addressInfo = await getAddressInfo(voterAddress) blocksMinted = addressInfo?.blocksMinted || 0 } catch (e) { console.warn(`Failed to get addressInfo for ${voterAddress}`, e) } const isAdmin = adminAddresses.includes(voterAddress) const isMinter = memberAddresses.includes(voterAddress) return { optionIndex, voterPublicKey, voterAddress, voterName, isAdmin, isMinter, blocksMinted } }) const allVoters = await Promise.all(voterPromises) const yesVoters = [] const noVoters = [] let totalMinterAndAdminYesWeight = 0 let totalMinterAndAdminNoWeight = 0 for (const v of allVoters) { if (v.optionIndex === 0) { yesVoters.push(v) totalMinterAndAdminYesWeight+=v.blocksMinted } else if (v.optionIndex === 1) { noVoters.push(v) totalMinterAndAdminNoWeight+=v.blocksMinted } } yesVoters.sort((a,b) => b.blocksMinted - a.blocksMinted) noVoters.sort((a,b) => b.blocksMinted - a.blocksMinted) const sortedAllVoters = allVoters.sort((a,b) => b.blocksMinted - a.blocksMinted) await createVoterMap(sortedAllVoters, cardIdentifier) const yesTableHtml = buildVotersTableHtml(yesVoters, /* tableColor= */ "green") const noTableHtml = buildVotersTableHtml(noVoters, /* tableColor= */ "red") const detailsHtml = `No voters here.
` } // Decide extremely dark background for the let bodyBackground if (tableColor === "green") { bodyBackground = "rgba(0, 18, 0, 0.8)" // near-black green } else if (tableColor === "red") { bodyBackground = "rgba(30, 0, 0, 0.8)" // near-black red } else { // fallback color if needed bodyBackground = "rgba(40, 20, 10, 0.8)" } // tableColor is used for the , bodyBackground for the const minterColor = 'rgb(98, 122, 167)' const adminColor = 'rgb(44, 209, 151)' const userColor = 'rgb(102, 102, 102)' return `Voter Name/Address | Voter Type | Voter Weight(=BlocksMinted) |
---|---|---|
${displayName} | ${userType} | ${v.blocksMinted} |
Existing ${transactionType} Approvals: ${uniqueApprovalCount}
${tableHtml}Existing ${transactionType} Approvals: ${uniqueApprovalCount}
${tableHtml}Existing ${transactionType} Approvals: ${uniqueApprovalCount}
${tableHtml}Existing ${transactionType} Approvals: ${uniqueApprovalCount}
${tableHtml}Existing ${transactionType} Approvals: ${uniqueApprovalCount}
${tableHtml}Admin Name | Approval Time |
---|
(has Blocks Penalty)
' const adjustmentText = addressInfo.blocksMintedAdjustment == 0 ? '' : '
(has Blocks Adjustment)
' try { const invites = await fetchGroupInvitesByAddress(address) const hasMinterInvite = invites.some((invite) => invite.groupId === 694) if (userVote === 0) { finalBgColor = "rgba(1, 65, 39, 0.41)"; // or any green you want } else if (userVote === 1) { finalBgColor = "rgba(107, 3, 3, 0.3)"; // or any red you want } else if (isExistingMinter){ finalBgColor = "rgb(99, 99, 99)" invitedText = `
${header}
${penaltyText}${adjustmentText}${invitedText}(click COMMENTS button to open/close card comments)
By: ${creator} - ${formattedDate}
${commenterName} ${adminBadge}
${commentDataResponse.content}
${timestamp}