Added ACTIONS for INVITE, KICK, and BAN to Minter and Admin Boards.
This commit is contained in:
parent
040d3fa184
commit
53cc5a5f2a
@ -10,6 +10,8 @@ let isTopic = false
|
|||||||
let attemptLoadAdminDataCount = 0
|
let attemptLoadAdminDataCount = 0
|
||||||
let adminMemberCount = 0
|
let adminMemberCount = 0
|
||||||
let adminPublicKeys = []
|
let adminPublicKeys = []
|
||||||
|
let kickTransactions = []
|
||||||
|
let banTransactions = []
|
||||||
|
|
||||||
console.log("Attempting to load AdminBoard.js")
|
console.log("Attempting to load AdminBoard.js")
|
||||||
|
|
||||||
@ -104,10 +106,45 @@ const loadAdminBoardPage = async () => {
|
|||||||
})
|
})
|
||||||
createScrollToTopButton()
|
createScrollToTopButton()
|
||||||
// await fetchAndValidateAllAdminCards()
|
// await fetchAndValidateAllAdminCards()
|
||||||
await fetchAllEncryptedCards()
|
|
||||||
await updateOrSaveAdminGroupsDataLocally()
|
await updateOrSaveAdminGroupsDataLocally()
|
||||||
|
await fetchAllKicKBanTxData()
|
||||||
|
await fetchAllEncryptedCards()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchAllKicKBanTxData = async () => {
|
||||||
|
const kickTxType = "GROUP_KICK"
|
||||||
|
const banTxType = "GROUP_BAN"
|
||||||
|
const banArray = [banTxType]
|
||||||
|
const kickArray = [kickTxType]
|
||||||
|
|
||||||
|
banTransactions = await searchTransactions({
|
||||||
|
txTypes: banArray,
|
||||||
|
address: '', // or whatever address
|
||||||
|
confirmationStatus: 'CONFIRMED',
|
||||||
|
limit: 0,
|
||||||
|
reverse: true,
|
||||||
|
offset: 0,
|
||||||
|
startBlock: 1990000,
|
||||||
|
blockLimit: 0,
|
||||||
|
txGroupId: 0
|
||||||
|
});
|
||||||
|
console.warn(`banTxData`, banTransactions)
|
||||||
|
|
||||||
|
kickTransactions = await searchTransactions({
|
||||||
|
txTypes: kickArray,
|
||||||
|
address: '',
|
||||||
|
confirmationStatus: 'CONFIRMED',
|
||||||
|
limit: 0,
|
||||||
|
reverse: true,
|
||||||
|
offset: 0,
|
||||||
|
startBlock: 1990000,
|
||||||
|
blockLimit: 0,
|
||||||
|
txGroupId: 0
|
||||||
|
});
|
||||||
|
console.warn(`kickTxData`, kickTransactions)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Example: fetch and save admin public keys and count
|
// Example: fetch and save admin public keys and count
|
||||||
const updateOrSaveAdminGroupsDataLocally = async () => {
|
const updateOrSaveAdminGroupsDataLocally = async () => {
|
||||||
try {
|
try {
|
||||||
@ -178,154 +215,6 @@ const extractEncryptedCardsMinterName = (cardIdentifier) => {
|
|||||||
return minterName
|
return minterName
|
||||||
}
|
}
|
||||||
|
|
||||||
// const processCards = async (validEncryptedCards) => {
|
|
||||||
// const latestCardsMap = new Map()
|
|
||||||
|
|
||||||
// await Promise.all(validEncryptedCards.map(async card => {
|
|
||||||
// const timestamp = card.updated || card.created || 0
|
|
||||||
// const existingCard = latestCardsMap.get(card.identifier)
|
|
||||||
|
|
||||||
// if (!existingCard || timestamp > (existingCard.updated || existingCard.created || 0)) {
|
|
||||||
// latestCardsMap.set(card.identifier, card)
|
|
||||||
// }
|
|
||||||
// }))
|
|
||||||
|
|
||||||
// console.log(`latestCardsMap, by timestamp`, latestCardsMap)
|
|
||||||
|
|
||||||
// const uniqueValidCards = Array.from(latestCardsMap.values())
|
|
||||||
|
|
||||||
// return uniqueValidCards
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
//Main function to load the Minter Cards ----------------------------------------
|
|
||||||
//TODO verify the latest changes work
|
|
||||||
// const fetchAllEncryptedCards = async (isRefresh=false) => {
|
|
||||||
// const encryptedCardsContainer = document.getElementById("encrypted-cards-container")
|
|
||||||
// encryptedCardsContainer.innerHTML = "<p>Loading cards...</p>"
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// const response = await searchSimple('MAIL_PRIVATE', `${encryptedCardIdentifierPrefix}`, '', 0)
|
|
||||||
|
|
||||||
// if (!response || !Array.isArray(response) || response.length === 0) {
|
|
||||||
// encryptedCardsContainer.innerHTML = "<p>No cards found.</p>"
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Validate cards and filter
|
|
||||||
// const validatedEncryptedCards = await Promise.all(
|
|
||||||
// response.map(async card => {
|
|
||||||
// const isValid = await validateEncryptedCardIdentifier(card)
|
|
||||||
// return isValid ? card : null
|
|
||||||
// })
|
|
||||||
// )
|
|
||||||
// console.log(`validatedEncryptedCards:`, validatedEncryptedCards, `... running next filter...`)
|
|
||||||
|
|
||||||
// const validEncryptedCards = validatedEncryptedCards.filter(card => card !== null)
|
|
||||||
// console.log(`validEncryptedcards:`, validEncryptedCards)
|
|
||||||
|
|
||||||
// if (validEncryptedCards.length === 0) {
|
|
||||||
// encryptedCardsContainer.innerHTML = "<p>No valid cards found.</p>";
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// const finalCards = await processCards(validEncryptedCards)
|
|
||||||
|
|
||||||
// console.log(`finalCards:`,finalCards)
|
|
||||||
// // Display skeleton cards immediately
|
|
||||||
// encryptedCardsContainer.innerHTML = ""
|
|
||||||
// finalCards.forEach(card => {
|
|
||||||
// const skeletonHTML = createSkeletonCardHTML(card.identifier)
|
|
||||||
// encryptedCardsContainer.insertAdjacentHTML("beforeend", skeletonHTML)
|
|
||||||
// })
|
|
||||||
|
|
||||||
// // Fetch and update each card
|
|
||||||
// finalCards.forEach(async card => {
|
|
||||||
// try {
|
|
||||||
// // const hasMinterName = await extractEncryptedCardsMinterName(card.identifier)
|
|
||||||
// // if (hasMinterName) existingCardMinterNames.push(hasMinterName)
|
|
||||||
|
|
||||||
// const cardDataResponse = await qortalRequest({
|
|
||||||
// action: "FETCH_QDN_RESOURCE",
|
|
||||||
// name: card.name,
|
|
||||||
// service: "MAIL_PRIVATE",
|
|
||||||
// identifier: card.identifier,
|
|
||||||
// encoding: "base64"
|
|
||||||
// })
|
|
||||||
|
|
||||||
// if (!cardDataResponse) {
|
|
||||||
// console.warn(`Skipping invalid card: ${JSON.stringify(card)}`)
|
|
||||||
// removeSkeleton(card.identifier)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const decryptedCardData = await decryptAndParseObject(cardDataResponse)
|
|
||||||
|
|
||||||
// // Skip cards without polls
|
|
||||||
// if (!decryptedCardData.poll) {
|
|
||||||
// console.warn(`Skipping card with no poll: ${card.identifier}`)
|
|
||||||
// removeSkeleton(card.identifier)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const encryptedCardPollPublisherPublicKey = await getPollPublisherPublicKey(decryptedCardData.poll)
|
|
||||||
// const encryptedCardPublisherPublicKey = await getPublicKeyByName(card.name)
|
|
||||||
|
|
||||||
// if (encryptedCardPollPublisherPublicKey != encryptedCardPublisherPublicKey) {
|
|
||||||
// console.warn(`QuickMythril cardPollHijack attack found! Not including card with identifier: ${card.identifier}`)
|
|
||||||
// removeSkeleton(card.identifier)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Fetch poll results and discard cards with no results
|
|
||||||
// const pollResults = await fetchPollResults(decryptedCardData.poll)
|
|
||||||
|
|
||||||
// if (pollResults?.error) {
|
|
||||||
// console.warn(`Skipping card with failed poll results?: ${card.identifier}, poll=${decryptedCardData.poll}`)
|
|
||||||
// removeSkeleton(card.identifier)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (!isRefresh) {
|
|
||||||
// console.log(`This is a REFRESH, NOT adding names to duplicates list...`)
|
|
||||||
// const obtainedMinterName = decryptedCardData.minterName
|
|
||||||
|
|
||||||
// // if ((obtainedMinterName) && existingCardMinterNames.includes(obtainedMinterName)) {
|
|
||||||
// // console.warn(`name found in existing names array...${obtainedMinterName} skipping duplicate card...${card.identifier}`)
|
|
||||||
// // removeSkeleton(card.identifier)
|
|
||||||
// // return
|
|
||||||
// // } else if ((obtainedMinterName) && (!existingCardMinterNames.includes(obtainedMinterName))) {
|
|
||||||
// // existingCardMinterNames.push(obtainedMinterName)
|
|
||||||
// // console.log(`minterName: ${obtainedMinterName} found, doesn't exist in existing array, added to existingCardMinterNames array`)
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// if (obtainedMinterName && existingCardMinterNames.some(item => item.minterName === obtainedMinterName)) {
|
|
||||||
// console.warn(`name found in existing names array...${obtainedMinterName} skipping duplicate card...${card.identifier}`)
|
|
||||||
// removeSkeleton(card.identifier)
|
|
||||||
// return
|
|
||||||
// } else if (obtainedMinterName) {
|
|
||||||
// existingCardMinterNames.push({ minterName: obtainedMinterName, identifier: card.identifier })
|
|
||||||
// console.log(`Added minterName and identifier to existingCardMinterNames array:`, { minterName: obtainedMinterName, identifier: card.identifier })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // const minterNameFromIdentifier = await extractCardsMinterName(card.identifier);
|
|
||||||
// const encryptedCommentCount = await getEncryptedCommentCount(card.identifier)
|
|
||||||
// // Generate final card HTML
|
|
||||||
|
|
||||||
// const finalCardHTML = await createEncryptedCardHTML(decryptedCardData, pollResults, card.identifier, encryptedCommentCount)
|
|
||||||
// replaceSkeleton(card.identifier, finalCardHTML)
|
|
||||||
// } catch (error) {
|
|
||||||
// console.error(`Error processing card ${card.identifier}:`, error)
|
|
||||||
// removeSkeleton(card.identifier)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// } catch (error) {
|
|
||||||
// console.error("Error loading cards:", error)
|
|
||||||
// encryptedCardsContainer.innerHTML = "<p>Failed to load cards.</p>"
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
const fetchAllEncryptedCards = async (isRefresh = false) => {
|
const fetchAllEncryptedCards = async (isRefresh = false) => {
|
||||||
const encryptedCardsContainer = document.getElementById("encrypted-cards-container")
|
const encryptedCardsContainer = document.getElementById("encrypted-cards-container")
|
||||||
encryptedCardsContainer.innerHTML = "<p>Loading cards...</p>"
|
encryptedCardsContainer.innerHTML = "<p>Loading cards...</p>"
|
||||||
@ -479,22 +368,6 @@ const fetchAllEncryptedCards = async (isRefresh = false) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//TODO verify that this actually isn't necessary. if not, remove it.
|
|
||||||
// const removeEncryptedSkeleton = (cardIdentifier) => {
|
|
||||||
// const encryptedSkeletonCard = document.getElementById(`skeleton-${cardIdentifier}`)
|
|
||||||
// if (encryptedSkeletonCard) {
|
|
||||||
// encryptedSkeletonCard.remove(); // Remove the skeleton silently
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const replaceEncryptedSkeleton = (cardIdentifier, htmlContent) => {
|
|
||||||
// const encryptedSkeletonCard = document.getElementById(`skeleton-${cardIdentifier}`)
|
|
||||||
// if (encryptedSkeletonCard) {
|
|
||||||
// encryptedSkeletonCard.outerHTML = htmlContent;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Function to create a skeleton card
|
// Function to create a skeleton card
|
||||||
const createEncryptedSkeletonCardHTML = (cardIdentifier) => {
|
const createEncryptedSkeletonCardHTML = (cardIdentifier) => {
|
||||||
return `
|
return `
|
||||||
@ -911,28 +784,44 @@ const processQortalLinkForRendering = async (link) => {
|
|||||||
return link
|
return link
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkAndDisplayRemoveActions = async (adminYes, creator, cardIdentifier) => {
|
const checkAndDisplayRemoveActions = async (adminYes, name, cardIdentifier) => {
|
||||||
const latestBlockInfo = await getLatestBlockInfo()
|
const latestBlockInfo = await getLatestBlockInfo()
|
||||||
const isBlockPassed = latestBlockInfo.height >= GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT
|
const isBlockPassed = latestBlockInfo.height >= GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT
|
||||||
let minAdminCount = 9
|
let minAdminCount
|
||||||
const minterAdmins = await fetchMinterGroupAdmins()
|
const minterAdmins = await fetchMinterGroupAdmins()
|
||||||
|
|
||||||
if ((minterAdmins) && (minterAdmins.length === 1)){
|
if ((minterAdmins) && (minterAdmins.length === 1)){
|
||||||
console.warn(`simply a double-check that there is only one MINTER group admin, in which case the group hasn't been transferred to null...keeping default minAdminCount of: ${minAdminCount}`)
|
console.warn(`simply a double-check that there is only one MINTER group admin, in which case the group hasn't been transferred to null...keeping default minAdminCount of: ${minAdminCount}`)
|
||||||
|
minAdminCount = 9
|
||||||
} else if ((minterAdmins) && (minterAdmins.length > 1) && isBlockPassed){
|
} else if ((minterAdmins) && (minterAdmins.length > 1) && isBlockPassed){
|
||||||
const totalAdmins = minterAdmins.length
|
const totalAdmins = minterAdmins.length
|
||||||
const fortyPercent = totalAdmins * 0.40
|
const fortyPercent = totalAdmins * 0.40
|
||||||
minAdminCount = Math.round(fortyPercent)
|
minAdminCount = Math.round(fortyPercent)
|
||||||
console.warn(`this is another check to ensure minterAdmin group has more than 1 admin. IF so we will calculate the 40% needed for GROUP_APPROVAL, that number is: ${minAdminCount}`)
|
console.warn(`this is another check to ensure minterAdmin group has more than 1 admin. IF so we will calculate the 40% needed for GROUP_APPROVAL, that number is: ${minAdminCount}`)
|
||||||
}
|
}
|
||||||
//TODO verify the above functionality to calculate 40% of MINTER group admins, and use that for minAdminCount
|
if (isBlockPassed && userState.isMinterAdmin) {
|
||||||
|
console.warn(`feature trigger has passed, checking for approval requirements`)
|
||||||
if (adminYes >= minAdminCount && userState.isMinterAdmin && !isBlockPassed) {
|
const addressInfo = await getNameInfo(name)
|
||||||
const removeButtonHtml = createRemoveButtonHtml(creator, cardIdentifier)
|
const address = addressInfo.owner
|
||||||
return removeButtonHtml
|
const kickApprovalHtml = await checkGroupApprovalAndCreateButton(address, cardIdentifier, "GROUP_KICK")
|
||||||
|
const banApprovalHtml = await checkGroupApprovalAndCreateButton(address, cardIdentifier, "GROUP_BAN")
|
||||||
|
|
||||||
|
if (kickApprovalHtml) {
|
||||||
|
return kickApprovalHtml
|
||||||
|
}
|
||||||
|
|
||||||
|
if (banApprovalHtml) {
|
||||||
|
return banApprovalHtml
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ''
|
|
||||||
|
if (adminYes >= minAdminCount && userState.isMinterAdmin) {
|
||||||
|
const removeButtonHtml = createRemoveButtonHtml(name, cardIdentifier)
|
||||||
|
return removeButtonHtml
|
||||||
|
} else{
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const createRemoveButtonHtml = (name, cardIdentifier) => {
|
const createRemoveButtonHtml = (name, cardIdentifier) => {
|
||||||
@ -957,9 +846,12 @@ const createRemoveButtonHtml = (name, cardIdentifier) => {
|
|||||||
const handleKickMinter = async (minterName) => {
|
const handleKickMinter = async (minterName) => {
|
||||||
try {
|
try {
|
||||||
// Optional block check
|
// Optional block check
|
||||||
|
let txGroupId = 0
|
||||||
const { height: currentHeight } = await getLatestBlockInfo()
|
const { height: currentHeight } = await getLatestBlockInfo()
|
||||||
if (currentHeight <= GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT) {
|
const isBlockPassed = currentHeight >= GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT
|
||||||
console.log(`block height is under the removal featureTrigger height`)
|
if (isBlockPassed) {
|
||||||
|
console.log(`block height above featureTrigger Height, using group approval method...txGroupId 694`)
|
||||||
|
txGroupId = 694
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the minter address from name info
|
// Get the minter address from name info
|
||||||
@ -970,68 +862,82 @@ const handleKickMinter = async (minterName) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// The admin public key
|
|
||||||
const adminPublicKey = await getPublicKeyByName(userState.accountName)
|
const adminPublicKey = await getPublicKeyByName(userState.accountName)
|
||||||
|
const reason = 'Kicked by Minter Admins'
|
||||||
|
const fee = 0.01
|
||||||
|
|
||||||
// Create the raw remove transaction
|
const rawKickTransaction = await createGroupKickTransaction(minterAddress, adminPublicKey, 694, minterAddress, reason, txGroupId, fee)
|
||||||
const rawKickTransaction = await createGroupKickTransaction(minterAddress, adminPublicKey, 694, minterAddress)
|
|
||||||
|
|
||||||
// Sign the transaction
|
|
||||||
const signedKickTransaction = await qortalRequest({
|
const signedKickTransaction = await qortalRequest({
|
||||||
action: "SIGN_TRANSACTION",
|
action: "SIGN_TRANSACTION",
|
||||||
unsignedBytes: rawKickTransaction
|
unsignedBytes: rawKickTransaction
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let txToProcess = signedKickTransaction
|
||||||
|
|
||||||
// Process the transaction
|
const processKickTx = await processTransaction(txToProcess)
|
||||||
const processResponse = await processTransaction(signedKickTransaction)
|
|
||||||
|
|
||||||
if (processResponse?.status === "OK") {
|
if (typeof processKickTx === 'object') {
|
||||||
alert(`${minterName}'s KICK transaction has been SUCCESSFULLY PROCESSED. Please WAIT FOR CONFIRMATION...`)
|
console.log("transaction success object:", processKickTx)
|
||||||
|
alert(`${minterName} kick successfully issued! Wait for confirmation...Transaction Response: ${JSON.stringify(processKickTx)}`)
|
||||||
} else {
|
} else {
|
||||||
alert("Failed to process the removal transaction.")
|
console.log("transaction raw text response:", processKickTx)
|
||||||
|
alert(`TxResponse: ${JSON.stringify(processKickTx)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error removing minter:", error)
|
console.error("Error removing minter:", error)
|
||||||
alert("Error removing minter. Please try again.")
|
alert(`Error:${error}. Please try again.`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleBanMinter = async (minterName) => {
|
const handleBanMinter = async (minterName) => {
|
||||||
try {
|
try {
|
||||||
|
let txGroupId = 0
|
||||||
const { height: currentHeight } = await getLatestBlockInfo()
|
const { height: currentHeight } = await getLatestBlockInfo()
|
||||||
if (currentHeight <= GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT) {
|
if (currentHeight <= GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT) {
|
||||||
console.log(`block height is under the removal featureTrigger height`)
|
console.log(`block height is under the removal featureTrigger height, using txGroupId 0`)
|
||||||
|
txGroupId = 0
|
||||||
|
} else {
|
||||||
|
console.log(`featureTrigger block is passed, using txGroupId 694`)
|
||||||
|
txGroupId = 694
|
||||||
}
|
}
|
||||||
|
|
||||||
const minterInfo = await getNameInfo(minterName)
|
const minterInfo = await getNameInfo(minterName)
|
||||||
const minterAddress = minterInfo?.owner
|
const minterAddress = minterInfo?.owner
|
||||||
|
|
||||||
if (!minterAddress) {
|
if (!minterAddress) {
|
||||||
alert(`No valid address found for minter name: ${minterName}`)
|
alert(`No valid address found for minter name: ${minterName}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const adminPublicKey = await getPublicKeyByName(userState.accountName)
|
const adminPublicKey = await getPublicKeyByName(userState.accountName)
|
||||||
|
const reason = 'Banned by Minter Admins'
|
||||||
|
const fee = 0.01
|
||||||
|
|
||||||
const rawBanTransaction = await createGroupBanTransaction(minterAddress, adminPublicKey, 694, minterAddress)
|
const rawBanTransaction = await createGroupBanTransaction(minterAddress, adminPublicKey, 694, minterAddress, reason, txGroupId, fee)
|
||||||
|
|
||||||
const signedBanTransaction = await qortalRequest({
|
const signedBanTransaction = await qortalRequest({
|
||||||
action: "SIGN_TRANSACTION",
|
action: "SIGN_TRANSACTION",
|
||||||
unsignedBytes: rawBanTransaction
|
unsignedBytes: rawBanTransaction
|
||||||
})
|
})
|
||||||
|
|
||||||
const processResponse = await processTransaction(signedBanTransaction)
|
let txToProcess = signedBanTransaction
|
||||||
|
|
||||||
if (processResponse?.status === "OK") {
|
const processedTx = await processTransaction(txToProcess)
|
||||||
alert(`${minterName}'s BAN transaction has been SUCCESSFULLY PROCESSED. Please WAIT FOR CONFIRMATION...`)
|
|
||||||
|
if (typeof processedTx === 'object') {
|
||||||
|
console.log("transaction success object:", processedTx)
|
||||||
|
alert(`${minterName} BAN successfully issued! Wait for confirmation...Transaction Response: ${JSON.stringify(processedTx)}`)
|
||||||
} else {
|
} else {
|
||||||
alert("Failed to process the removal transaction.")
|
// fallback string or something
|
||||||
|
console.log("transaction raw text response:", processedTx)
|
||||||
|
alert(`transaction response:${JSON.stringify(processedTx)}` )
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error removing minter:", error)
|
console.error("Error removing minter:", error)
|
||||||
alert("Error removing minter. Please try again.")
|
alert(`Error ${error}. Please try again.`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1062,7 +968,7 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardColorCode = showTopic ? '#0e1b15' : '#151f28'
|
let cardColorCode = showTopic ? '#0e1b15' : '#151f28'
|
||||||
|
|
||||||
const minterOrTopicHtml = ((showTopic) || (isUndefinedUser)) ? `
|
const minterOrTopicHtml = ((showTopic) || (isUndefinedUser)) ? `
|
||||||
<div class="support-header"><h5> REGARDING (Topic): </h5></div>
|
<div class="support-header"><h5> REGARDING (Topic): </h5></div>
|
||||||
@ -1080,11 +986,30 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
|
|||||||
createModal('poll-details')
|
createModal('poll-details')
|
||||||
|
|
||||||
let showRemoveHtml
|
let showRemoveHtml
|
||||||
|
let altText
|
||||||
const verifiedName = await validateMinterName(minterName)
|
const verifiedName = await validateMinterName(minterName)
|
||||||
|
|
||||||
if (verifiedName) {
|
if (verifiedName) {
|
||||||
|
const accountInfo = await getNameInfo(verifiedName)
|
||||||
|
const accountAddress = accountInfo.owner
|
||||||
console.log(`name is validated, utilizing for removal features...${verifiedName}`)
|
console.log(`name is validated, utilizing for removal features...${verifiedName}`)
|
||||||
const removeActionsHtml = await checkAndDisplayRemoveActions(adminYes, verifiedName, cardIdentifier)
|
const removeActionsHtml = await checkAndDisplayRemoveActions(adminYes, verifiedName, cardIdentifier)
|
||||||
showRemoveHtml = removeActionsHtml
|
showRemoveHtml = removeActionsHtml
|
||||||
|
|
||||||
|
if (banTransactions.some((banTx) => banTx.groupId === 694 && banTx.offender === accountAddress)){
|
||||||
|
console.warn(`account was already banned, displaying as such...`)
|
||||||
|
cardColorCode = 'rgb(24, 3, 3)'
|
||||||
|
altText = `<h4 style="color:rgb(106, 2, 2); margin-bottom: 0.5em;">BANNED From MINTER Group</h4>`
|
||||||
|
showRemoveHtml = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kickTransactions.some((kickTx) => kickTx.groupId === 694 && kickTx.member === accountAddress)){
|
||||||
|
console.warn(`account was already kicked, displaying as such...`)
|
||||||
|
cardColorCode = 'rgb(29, 7, 4)'
|
||||||
|
altText = `<h4 style="color:rgb(143, 117, 21); margin-bottom: 0.5em;">KICKED From MINTER Group</h4>`
|
||||||
|
showRemoveHtml = ''
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.log(`name could not be validated, assuming topic card (or some other issue with name validation) for removalActions`)
|
console.log(`name could not be validated, assuming topic card (or some other issue with name validation) for removalActions`)
|
||||||
showRemoveHtml = ''
|
showRemoveHtml = ''
|
||||||
@ -1098,6 +1023,7 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
|
|||||||
<h2>${creator}</h2>
|
<h2>${creator}</h2>
|
||||||
${minterOrTopicHtml}
|
${minterOrTopicHtml}
|
||||||
<p>${header}</p>
|
<p>${header}</p>
|
||||||
|
${altText}
|
||||||
</div>
|
</div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
${content}
|
${content}
|
||||||
|
@ -273,10 +273,10 @@ const loadCards = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pollResults = await fetchPollResults(cardDataResponse.poll)
|
const pollResults = await fetchPollResults(cardDataResponse.poll)
|
||||||
const BgColor = generateDarkPastelBackgroundBy(card.name)
|
const bgColor = generateDarkPastelBackgroundBy(card.name)
|
||||||
const commentCount = await countComments(card.identifier)
|
const commentCount = await countComments(card.identifier)
|
||||||
const cardUpdatedTime = card.updated || null
|
const cardUpdatedTime = card.updated || null
|
||||||
const finalCardHTML = await createCardHTML(cardDataResponse, pollResults, card.identifier, commentCount, cardUpdatedTime, BgColor)
|
const finalCardHTML = await createCardHTML(cardDataResponse, pollResults, card.identifier, commentCount, cardUpdatedTime, bgColor)
|
||||||
|
|
||||||
replaceSkeleton(card.identifier, finalCardHTML)
|
replaceSkeleton(card.identifier, finalCardHTML)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -1000,30 +1000,46 @@ const handleInviteMinter = async (minterName) => {
|
|||||||
try {
|
try {
|
||||||
const blockInfo = await getLatestBlockInfo()
|
const blockInfo = await getLatestBlockInfo()
|
||||||
const blockHeight = blockInfo.height
|
const blockHeight = blockInfo.height
|
||||||
if (blockHeight <= MINTER_INVITE_BLOCK_HEIGHT) {
|
|
||||||
console.log(`block height is under the featureTrigger height`)
|
|
||||||
}
|
|
||||||
const minterAccountInfo = await getNameInfo(minterName)
|
const minterAccountInfo = await getNameInfo(minterName)
|
||||||
const minterAddress = await minterAccountInfo.owner
|
const minterAddress = await minterAccountInfo.owner
|
||||||
const adminPublicKey = await getPublicKeyByName(userState.accountName)
|
let adminPublicKey
|
||||||
console.log(`about to attempt group invite, minterAddress: ${minterAddress}, adminPublicKey: ${adminPublicKey}`)
|
let txGroupId
|
||||||
const inviteTransaction = await createGroupInviteTransaction(minterAddress, adminPublicKey, 694, minterAddress, 864000, 0)
|
if (blockHeight >= GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT){
|
||||||
|
if (userState.isMinterAdmin){
|
||||||
|
adminPublicKey = await getPublicKeyByName(userState.accountName)
|
||||||
|
txGroupId = 694
|
||||||
|
}else{
|
||||||
|
console.warn(`user is not a minter admin, cannot create invite!`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
adminPublicKey = await getPublicKeyByName(userState.accountName)
|
||||||
|
txGroupId = 0
|
||||||
|
}
|
||||||
|
const fee = 0.01
|
||||||
|
const timeToLive = 864000
|
||||||
|
|
||||||
// Step 2: Sign the transaction using qortalRequest
|
console.log(`about to attempt group invite, minterAddress: ${minterAddress}, adminPublicKey: ${adminPublicKey}`)
|
||||||
|
const inviteTransaction = await createGroupInviteTransaction(minterAddress, adminPublicKey, 694, minterAddress, timeToLive, txGroupId, fee)
|
||||||
|
|
||||||
const signedTransaction = await qortalRequest({
|
const signedTransaction = await qortalRequest({
|
||||||
action: "SIGN_TRANSACTION",
|
action: "SIGN_TRANSACTION",
|
||||||
unsignedBytes: inviteTransaction
|
unsignedBytes: inviteTransaction
|
||||||
})
|
})
|
||||||
|
|
||||||
// Step 3: Process the transaction
|
|
||||||
console.warn(`signed transaction`,signedTransaction)
|
console.warn(`signed transaction`,signedTransaction)
|
||||||
const processResponse = await processTransaction(signedTransaction)
|
const processResponse = await processTransaction(signedTransaction)
|
||||||
|
|
||||||
if (processResponse?.status === "OK") {
|
if (typeof processResponse === 'object') {
|
||||||
alert(`${minterName} has been successfully invited, please WAIT FOR CONFIRMATION...`)
|
// The successful object might have a "signature" or "type" or "approvalStatus"
|
||||||
|
console.log("Invite transaction success object:", processResponse)
|
||||||
|
alert(`${minterName} has been successfully invited! Wait for confirmation...Transaction Response: ${JSON.stringify(processResponse)}`)
|
||||||
} else {
|
} else {
|
||||||
alert("Failed to process the invite transaction.")
|
// fallback string or something
|
||||||
|
console.log("Invite transaction raw text response:", processResponse)
|
||||||
|
alert(`Invite transaction response: ${JSON.stringify(processResponse)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error inviting minter:", error)
|
console.error("Error inviting minter:", error)
|
||||||
alert("Error inviting minter. Please try again.")
|
alert("Error inviting minter. Please try again.")
|
||||||
@ -1047,8 +1063,37 @@ const createInviteButtonHtml = (creator, cardIdentifier) => {
|
|||||||
const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) => {
|
const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) => {
|
||||||
const latestBlockInfo = await getLatestBlockInfo()
|
const latestBlockInfo = await getLatestBlockInfo()
|
||||||
const isBlockPassed = latestBlockInfo.height >= GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT
|
const isBlockPassed = latestBlockInfo.height >= GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT
|
||||||
|
let minAdminCount
|
||||||
|
const minterAdmins = await fetchMinterGroupAdmins()
|
||||||
|
|
||||||
if (adminYes >= 9 && userState.isMinterAdmin && !isBlockPassed) {
|
if (!isBlockPassed){
|
||||||
|
console.warn(`feature trigger not passed, using static number for minAdminCount`)
|
||||||
|
minAdminCount = 9
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((minterAdmins) && (minterAdmins.length === 1)){
|
||||||
|
console.warn(`simply a double-check that there is only one MINTER group admin, in which case the group hasn't been transferred to null...keeping default minAdminCount of: ${minAdminCount}`)
|
||||||
|
} else if ((minterAdmins) && (minterAdmins.length > 1) && isBlockPassed){
|
||||||
|
const totalAdmins = minterAdmins.length
|
||||||
|
const fortyPercent = totalAdmins * 0.40
|
||||||
|
minAdminCount = Math.round(fortyPercent)
|
||||||
|
console.warn(`this is another check to ensure minterAdmin group has more than 1 admin. IF so we will calculate the 40% needed for GROUP_APPROVAL, that number is: ${minAdminCount}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBlockPassed) {
|
||||||
|
const minterAddressInfo = await getNameInfo(creator)
|
||||||
|
const minterAddress = await minterAddressInfo.owner
|
||||||
|
if (userState.isMinterAdmin){
|
||||||
|
let groupApprovalHtml = await checkGroupApprovalAndCreateButton(minterAddress, cardIdentifier, 'GROUP_INVITE')
|
||||||
|
if (groupApprovalHtml) {
|
||||||
|
return groupApprovalHtml
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
console.log(`USER NOT ADMIN, no need for group approval buttons...`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (adminYes >= minAdminCount && userState.isMinterAdmin) {
|
||||||
const inviteButtonHtml = createInviteButtonHtml(creator, cardIdentifier)
|
const inviteButtonHtml = createInviteButtonHtml(creator, cardIdentifier)
|
||||||
console.log(`admin votes over 9, creating invite button...`, adminYes)
|
console.log(`admin votes over 9, creating invite button...`, adminYes)
|
||||||
return inviteButtonHtml
|
return inviteButtonHtml
|
||||||
@ -1057,6 +1102,146 @@ const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) =>
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transactionType) => {
|
||||||
|
const txTypes = [`${transactionType}`]
|
||||||
|
const confirmationStatus = 'CONFIRMED'
|
||||||
|
|
||||||
|
const groupApprovalSearchResults = await searchTransactions(txTypes, address, confirmationStatus, limit, reverse, offset)
|
||||||
|
const pendingApprovals = groupApprovalSearchResults.filter(tx => tx.approvalStatus === 'PENDING')
|
||||||
|
|
||||||
|
if (pendingApprovals) {
|
||||||
|
console.warn(`pendingApprovals FOUND: ${pendingApprovals}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingApprovals.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const txSig = pendingApprovals[0].signature
|
||||||
|
|
||||||
|
if (transactionType === `GROUP_INVITE`){
|
||||||
|
|
||||||
|
const approvalButtonHtml = `
|
||||||
|
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||||
|
<button
|
||||||
|
style="padding: 8px; background:rgb(37, 99, 44); color: #000; border: 1px solid #333; border-radius: 5px; cursor: pointer;"
|
||||||
|
onmouseover="this.style.backgroundColor='rgb(25, 47, 39) '"
|
||||||
|
onmouseout="this.style.backgroundColor='rgb(37, 99, 44) '"
|
||||||
|
onclick="handleGroupApproval('${address}','${txSig}')">
|
||||||
|
Approve Invite
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
return approvalButtonHtml
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transactionType === `GROUP_KICK`){
|
||||||
|
|
||||||
|
const approvalButtonHtml = `
|
||||||
|
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||||
|
<button
|
||||||
|
style="padding: 8px; background:rgb(119, 91, 21); color: #000; border: 1px solid #333; border-radius: 5px; cursor: pointer;"
|
||||||
|
onmouseover="this.style.backgroundColor='rgb(50, 52, 51) '"
|
||||||
|
onmouseout="this.style.backgroundColor='rgb(119, 91, 21) '"
|
||||||
|
onclick="handleGroupApproval('${address}','${txSig}')">
|
||||||
|
Approve Kick
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
return approvalButtonHtml
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transactionType === `GROUP_BAN`){
|
||||||
|
|
||||||
|
const approvalButtonHtml = `
|
||||||
|
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||||
|
<button
|
||||||
|
style="padding: 8px; background:rgb(54, 7, 7); color: #000; border: 1px solid #333; border-radius: 5px; cursor: pointer;"
|
||||||
|
onmouseover="this.style.backgroundColor='rgb(50, 52, 51) '"
|
||||||
|
onmouseout="this.style.backgroundColor='rgb(54, 7, 7) '"
|
||||||
|
onclick="handleGroupApproval('${address}','${txSig}')">
|
||||||
|
Approve Kick
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
return approvalButtonHtml
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGroupApproval = async (address, pendingApprovalSignature) => {
|
||||||
|
try{
|
||||||
|
if (!userState.isMinterAdmin) {
|
||||||
|
console.warn(`non-admin attempting to sign approval!`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const fee = 0.01
|
||||||
|
const adminPublicKey = await getPublicKeyByName(userState.accountName)
|
||||||
|
const txGroupId = 694
|
||||||
|
const rawGroupApprovalTransaction = await createGroupApprovalTransaction(address, adminPublicKey, pendingApprovalSignature, txGroupId, fee)
|
||||||
|
const signedGroupApprovalTransaction = await qortalRequest({
|
||||||
|
action: "SIGN_TRANSACTION",
|
||||||
|
unsignedBytes: rawGroupApprovalTransaction
|
||||||
|
})
|
||||||
|
// const switchToBase58 = isBase64(signedGroupApprovalTransaction)
|
||||||
|
let txToProcess = signedGroupApprovalTransaction
|
||||||
|
|
||||||
|
// if (switchToBase58){
|
||||||
|
// console.warn(`base64 tx found, converting to base58`,signedGroupApprovalTransaction)
|
||||||
|
// const convertedToHex = await base64ToHex(signedGroupApprovalTransaction)
|
||||||
|
// const base58TxData = await hexToBase58(convertedToHex)
|
||||||
|
// txToProcess = base58TxData
|
||||||
|
// console.log(`base58ConvertedSignedTxData to process:`,txToProcess)
|
||||||
|
// }
|
||||||
|
const processGroupApprovalTx = await processTransaction(txToProcess)
|
||||||
|
|
||||||
|
if (processGroupApprovalTx) {
|
||||||
|
alert(`transaction processed, please wait for CONFIRMATION: ${JSON.stringify(processGroupApprovalTx)}`)
|
||||||
|
} else {
|
||||||
|
alert(`creating tx failed for some reason`)
|
||||||
|
}
|
||||||
|
|
||||||
|
}catch(error){
|
||||||
|
console.error(error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleJoinGroup = async (minterAddress) => {
|
||||||
|
try{
|
||||||
|
if (userState.accountAddress === minterAddress) {
|
||||||
|
console.log(`minter user found `)
|
||||||
|
const joinerPublicKey = getPublicKeyFromAddress(minterAddress)
|
||||||
|
fee = 0.01
|
||||||
|
const joinGroupTransactionData = await createGroupJoinTransaction(minterAddress, joinerPublicKey, 694, 0, fee)
|
||||||
|
const signedJoinGroupTransaction = await qortalRequest({
|
||||||
|
action: "SIGN_TRANSACTION",
|
||||||
|
unsignedBytes: joinGroupTransactionData
|
||||||
|
})
|
||||||
|
let txToProcess = signedJoinGroupTransaction
|
||||||
|
// const switchToBase58 = isBase64(signedJoinGroupTransaction)
|
||||||
|
|
||||||
|
// if (switchToBase58){
|
||||||
|
// console.warn(`base64 tx found, converting to base58`, signedJoinGroupTransaction)
|
||||||
|
// const convertedToHex = await base64ToHex(signedJoinGroupTransaction)
|
||||||
|
// const base58TxData = await hexToBase58(convertedToHex)
|
||||||
|
// txToProcess = base58TxData
|
||||||
|
// console.log(`base58ConvertedSignedTxData to process:`,txToProcess)
|
||||||
|
// }
|
||||||
|
const processJoinGroupTransaction = await processTransaction(txToProcess)
|
||||||
|
|
||||||
|
if (processJoinGroupTransaction){
|
||||||
|
console.warn(`processed JOIN_GROUP tx`,processJoinGroupTransaction)
|
||||||
|
alert(`JOIN GROUP Transaction Processed Successfully, please WAIT FOR CONFIRMATION txData: ${JSON.stringify(processJoinGroupTransaction)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.warn(`user is not the minter`)
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
} catch(error){
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getMinterAvatar = async (minterName) => {
|
const getMinterAvatar = async (minterName) => {
|
||||||
const avatarUrl = `/arbitrary/THUMBNAIL/${minterName}/qortal_avatar`
|
const avatarUrl = `/arbitrary/THUMBNAIL/${minterName}/qortal_avatar`
|
||||||
try {
|
try {
|
||||||
@ -1076,7 +1261,7 @@ const getMinterAvatar = async (minterName) => {
|
|||||||
|
|
||||||
|
|
||||||
// Create the overall Minter Card HTML -----------------------------------------------
|
// Create the overall Minter Card HTML -----------------------------------------------
|
||||||
const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCount, cardUpdatedTime, BgColor) => {
|
const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCount, cardUpdatedTime, bgColor) => {
|
||||||
const { header, content, links, creator, timestamp, poll } = cardData
|
const { header, content, links, creator, timestamp, poll } = cardData
|
||||||
const formattedDate = cardUpdatedTime ? new Date(cardUpdatedTime).toLocaleString() : new Date(timestamp).toLocaleString()
|
const formattedDate = cardUpdatedTime ? new Date(cardUpdatedTime).toLocaleString() : new Date(timestamp).toLocaleString()
|
||||||
const avatarHtml = await getMinterAvatar(creator)
|
const avatarHtml = await getMinterAvatar(creator)
|
||||||
@ -1093,14 +1278,48 @@ const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCoun
|
|||||||
createModal('poll-details')
|
createModal('poll-details')
|
||||||
|
|
||||||
const inviteButtonHtml = await checkAndDisplayInviteButton(adminYes, creator, cardIdentifier)
|
const inviteButtonHtml = await checkAndDisplayInviteButton(adminYes, creator, cardIdentifier)
|
||||||
const inviteHtmlAdd = (inviteButtonHtml) ? inviteButtonHtml : ''
|
let inviteHtmlAdd = (inviteButtonHtml) ? inviteButtonHtml : ''
|
||||||
|
|
||||||
|
let finalBgColor = bgColor
|
||||||
|
let invitedText = "" // for "INVITED" label if found
|
||||||
|
|
||||||
|
try {
|
||||||
|
const minterAddress = await fetchOwnerAddressFromName(creator)
|
||||||
|
const invites = await fetchGroupInvitesByAddress(minterAddress)
|
||||||
|
const hasMinterInvite = invites.some((invite) => invite.groupId === 694)
|
||||||
|
if (hasMinterInvite) {
|
||||||
|
// If so, override background color & add an "INVITED" label
|
||||||
|
finalBgColor = "black";
|
||||||
|
invitedText = `<h4 style="color: gold; margin-bottom: 0.5em;">INVITED</h4>`
|
||||||
|
if (userState.accountName === creator){ //Check also if the creator is the user, and display the join group button if so.
|
||||||
|
inviteHtmlAdd = `
|
||||||
|
<div id="join-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||||
|
<button
|
||||||
|
style="padding: 8px; background:rgb(37, 99, 44); color: #000; border: 1px solid #333; border-radius: 5px; cursor: pointer;"
|
||||||
|
onmouseover="this.style.backgroundColor='rgb(25, 47, 39) '"
|
||||||
|
onmouseout="this.style.backgroundColor='rgb(37, 99, 44) '"
|
||||||
|
onclick="handleJoinGroup('${userState.accountAddress}')">
|
||||||
|
Approve Invite
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}else{
|
||||||
|
console.log(`user is not the minter... displaying no join button`)
|
||||||
|
inviteHtmlAdd = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//do not display invite button as they're already invited. Create a join button instead.
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error checking invites for user:", error)
|
||||||
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="minter-card" style="background-color: ${BgColor}">
|
<div class="minter-card" style="background-color: ${finalBgColor}">
|
||||||
<div class="minter-card-header">
|
<div class="minter-card-header">
|
||||||
${avatarHtml}
|
${avatarHtml}
|
||||||
<h3>${creator}</h3>
|
<h3>${creator}</h3>
|
||||||
<p>${header}</p>
|
<p>${header}</p>
|
||||||
|
${invitedText}
|
||||||
</div>
|
</div>
|
||||||
<div class="support-header"><h5>USER'S POST</h5></div>
|
<div class="support-header"><h5>USER'S POST</h5></div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
|
@ -28,6 +28,7 @@ const uid = async () => {
|
|||||||
console.log('Generated uid:', result)
|
console.log('Generated uid:', result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// a non-async version of the uid function, in case non-async functions need it. Ultimately we can probably remove uid but need to ensure no apps are using it asynchronously first. so this is kept for that purpose for now.
|
// a non-async version of the uid function, in case non-async functions need it. Ultimately we can probably remove uid but need to ensure no apps are using it asynchronously first. so this is kept for that purpose for now.
|
||||||
const randomID = () => {
|
const randomID = () => {
|
||||||
console.log('randomID non-async')
|
console.log('randomID non-async')
|
||||||
@ -40,6 +41,7 @@ const randomID = () => {
|
|||||||
console.log('Generated uid:', result)
|
console.log('Generated uid:', result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Turn a unix timestamp into a human-readable date
|
// Turn a unix timestamp into a human-readable date
|
||||||
const timestampToHumanReadableDate = async(timestamp) => {
|
const timestampToHumanReadableDate = async(timestamp) => {
|
||||||
const date = new Date(timestamp)
|
const date = new Date(timestamp)
|
||||||
@ -54,6 +56,72 @@ const timestampToHumanReadableDate = async(timestamp) => {
|
|||||||
console.log('Formatted date:', formattedDate)
|
console.log('Formatted date:', formattedDate)
|
||||||
return formattedDate
|
return formattedDate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// function to check if something is base58
|
||||||
|
const isBase58 = (str) => {
|
||||||
|
if (typeof str !== 'string' || !str.length) return false
|
||||||
|
// Basic regex for typical Base58 alphabet:
|
||||||
|
// 1) No zero-like chars (0, O, I, l).
|
||||||
|
// 2) Should be [1-9A-HJ-NP-Za-km-z].
|
||||||
|
const base58Regex = /^[1-9A-HJ-NP-Za-km-z]+$/
|
||||||
|
return base58Regex.test(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
//function to check if something is base64
|
||||||
|
const isBase64 = (str, attemptDecode = false) => {
|
||||||
|
if (typeof str !== 'string') return false
|
||||||
|
|
||||||
|
// Basic length mod check for classic Base64
|
||||||
|
if (str.length % 4 !== 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regex for valid Base64 chars + optional = padding
|
||||||
|
const base64Regex = /^[A-Za-z0-9+/]*(={1,2})?$/
|
||||||
|
if (!base64Regex.test(str)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attemptDecode) {
|
||||||
|
try {
|
||||||
|
// In browser, atob can throw if invalid
|
||||||
|
atob(str)
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const base64ToHex = async (base64 = 'string') => {
|
||||||
|
try {
|
||||||
|
const response = await fetch (`${baseUrl}/utils/frombase64`, {
|
||||||
|
headers: { 'Accept': 'text/plain' },
|
||||||
|
method: 'GET',
|
||||||
|
body: base64
|
||||||
|
})
|
||||||
|
const hex = await response.text()
|
||||||
|
return hex
|
||||||
|
}catch(error){
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hexToBase58 = async (hex = 'string') => {
|
||||||
|
try {
|
||||||
|
const response = await fetch (`${baseUrl}/utils/tobase58`, {
|
||||||
|
headers: { 'Accept': 'text/plain' },
|
||||||
|
method: 'GET',
|
||||||
|
body: hex
|
||||||
|
})
|
||||||
|
const base58 = await response.text()
|
||||||
|
return base58
|
||||||
|
}catch(error){
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Base64 encode a string
|
// Base64 encode a string
|
||||||
const base64EncodeString = async (str) => {
|
const base64EncodeString = async (str) => {
|
||||||
const encodedString = btoa(String.fromCharCode.apply(null, new Uint8Array(new TextEncoder().encode(str).buffer)))
|
const encodedString = btoa(String.fromCharCode.apply(null, new Uint8Array(new TextEncoder().encode(str).buffer)))
|
||||||
@ -260,9 +328,14 @@ const getNameInfo = async (name) => {
|
|||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.warn(`Failed to fetch name info for: ${name}, status: ${response.status}`)
|
console.warn(`Failed to fetch name info for: ${name}, status: ${response.status}`)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
if (!data.name) {
|
||||||
|
console.warn(`no name info returned, is this not a real registeredName? ${data.name} - returning null to bypass errors...`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Fetched name info:', data)
|
console.log('Fetched name info:', data)
|
||||||
return {
|
return {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
@ -514,6 +587,35 @@ const fetchAdminGroupsMembersPublicKeys = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchGroupInvitesByAddress = async (address) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${baseUrl}/groups/invites/${encodeURIComponent(address)}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
// Not a 2xx status; read error details
|
||||||
|
const errorText = await response.text()
|
||||||
|
throw new Error(`Failed to fetch group invites: HTTP ${response.status}, ${errorText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to parse the JSON response
|
||||||
|
const invites = await response.json()
|
||||||
|
|
||||||
|
// Example check: ensures the result is an array
|
||||||
|
if (!Array.isArray(invites)) {
|
||||||
|
throw new Error('Group invites response is not an array as expected.')
|
||||||
|
}
|
||||||
|
|
||||||
|
return invites // e.g. [{ groupId, inviter, invitee, expiry }, ...]
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching address group invites:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// QDN data calls --------------------------------------------------------------------------------------------------
|
// QDN data calls --------------------------------------------------------------------------------------------------
|
||||||
const searchLatestDataByIdentifier = async (identifier) => {
|
const searchLatestDataByIdentifier = async (identifier) => {
|
||||||
@ -1177,53 +1279,81 @@ const voteYesOnPoll = async (poll) => {
|
|||||||
|
|
||||||
// Qortal Transaction-related calls ---------------------------------------------------------------------------
|
// Qortal Transaction-related calls ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const processTransaction = async (rawTransaction) => {
|
const processTransaction = async (signedTransaction) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${baseUrl}/transactions/process`, {
|
const response = await fetch(`${baseUrl}/transactions/process`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'text/plain',
|
'Accept': 'text/plain', // or 'application/json' if the API states so
|
||||||
'X-API-VERSION': '2',
|
'X-API-VERSION': '2', // version 2
|
||||||
'Content-Type': 'text/plain'
|
'Content-Type': 'text/plain'
|
||||||
},
|
},
|
||||||
body: rawTransaction
|
body: signedTransaction
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) throw new Error(`Transaction processing failed: ${response.status}`)
|
if (!response.ok) {
|
||||||
|
// On error, read the text so we can see the error details
|
||||||
const result = await response.text()
|
const errorText = await response.text();
|
||||||
console.log("Transaction successfully processed:", result)
|
throw new Error(`Transaction processing failed: ${errorText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the content type to see how to parse
|
||||||
|
const contentType = response.headers.get('Content-Type') || ''
|
||||||
|
|
||||||
|
// If the core actually sets Content-Type: application/json
|
||||||
|
if (contentType.includes('application/json')) {
|
||||||
|
// We can do .json()
|
||||||
|
const result = await response.json();
|
||||||
|
console.log("Transaction processed, got JSON:", result);
|
||||||
return result
|
return result
|
||||||
|
} else {
|
||||||
|
// The core returns raw text that is actually JSON
|
||||||
|
const rawText = await response.text();
|
||||||
|
console.log("Raw text from server (version 2 means JSON string in text):", rawText)
|
||||||
|
|
||||||
|
// Attempt to parse if it’s indeed JSON
|
||||||
|
let parsed;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(rawText);
|
||||||
|
} catch {
|
||||||
|
// If it's not valid JSON, we can at least return the raw text
|
||||||
|
console.warn("Server returned non-JSON text (version 2 mismatch?).")
|
||||||
|
return rawText
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error processing transaction:", error)
|
console.error("Error processing transaction:", error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Create a group invite transaction. This will utilize a default timeToLive (which is how long the tx will be alive, not the time until it IS live...) of 10 days in seconds, as the legacy UI has a bug that doesn't display invites older than 10 days.
|
// Create a group invite transaction. This will utilize a default timeToLive (which is how long the tx will be alive, not the time until it IS live...) of 10 days in seconds, as the legacy UI has a bug that doesn't display invites older than 10 days.
|
||||||
// We will also default to the MINTER group for groupId, AFTER the GROUP_APPROVAL changes, the txGroupId will need to be set for tx that require approval.
|
// We will also default to the MINTER group for groupId, AFTER the GROUP_APPROVAL changes, the txGroupId will need to be set for tx that require approval.
|
||||||
const createGroupInviteTransaction = async (recipientAddress, adminPublicKey, groupId=694, invitee, timeToLive = 864000, txGroupId = 0, fee=0.01) => {
|
const createGroupInviteTransaction = async (recipientAddress, adminPublicKey, groupId=694, invitee, timeToLive, txGroupId, fee) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch account reference correctly
|
// Fetch account reference correctly
|
||||||
const accountInfo = await getAddressInfo(recipientAddress)
|
const accountInfo = await getAddressInfo(recipientAddress)
|
||||||
const accountReference = accountInfo.reference
|
const accountReference = accountInfo.reference
|
||||||
|
|
||||||
// Validate inputs before making the request
|
// Validate inputs before making the request
|
||||||
if (!adminPublicKey || !accountReference || !recipientAddress) {
|
if (!adminPublicKey || !accountReference) {
|
||||||
throw new Error("Missing required parameters for group invite transaction.")
|
throw new Error("Missing required parameters for group invite transaction.")
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
reference: accountReference,
|
reference: accountReference,
|
||||||
fee: fee || 0.01,
|
fee,
|
||||||
txGroupId: txGroupId,
|
txGroupId: txGroupId || 0,
|
||||||
recipient: recipientAddress,
|
recipient: null,
|
||||||
adminPublicKey: adminPublicKey,
|
adminPublicKey,
|
||||||
groupId: groupId,
|
groupId,
|
||||||
invitee: invitee || recipientAddress,
|
invitee: invitee || recipientAddress,
|
||||||
timeToLive: timeToLive
|
timeToLive
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Sending group invite transaction payload:", payload)
|
console.log("Sending group invite transaction payload:", payload)
|
||||||
@ -1251,7 +1381,7 @@ const createGroupInviteTransaction = async (recipientAddress, adminPublicKey, gr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createGroupKickTransaction = async (recipientAddress, adminPublicKey, groupId=694, member, reason='Kicked by admins', txGroupId = 0, fee=0.01) => {
|
const createGroupKickTransaction = async (recipientAddress, adminPublicKey, groupId=694, member, reason='Kicked by admins', txGroupId, fee) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch account reference correctly
|
// Fetch account reference correctly
|
||||||
@ -1266,16 +1396,16 @@ const createGroupKickTransaction = async (recipientAddress, adminPublicKey, grou
|
|||||||
const payload = {
|
const payload = {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
reference: accountReference,
|
reference: accountReference,
|
||||||
fee: fee | 0.01,
|
fee,
|
||||||
txGroupId: txGroupId,
|
txGroupId,
|
||||||
recipient: recipientAddress,
|
recipient: null,
|
||||||
adminPublicKey: adminPublicKey,
|
adminPublicKey,
|
||||||
groupId: groupId,
|
groupId: groupId,
|
||||||
member: member || recipientAddress,
|
member: member || recipientAddress,
|
||||||
reason: reason
|
reason: reason
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Sending group invite transaction payload:", payload)
|
console.log("Sending GROUP_KICK transaction payload:", payload)
|
||||||
|
|
||||||
const response = await fetch(`${baseUrl}/groups/kick`, {
|
const response = await fetch(`${baseUrl}/groups/kick`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -1295,12 +1425,109 @@ const createGroupKickTransaction = async (recipientAddress, adminPublicKey, grou
|
|||||||
console.log("Raw unsigned transaction created:", rawTransaction)
|
console.log("Raw unsigned transaction created:", rawTransaction)
|
||||||
return rawTransaction
|
return rawTransaction
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating group invite transaction:", error)
|
console.error("Error creating GROUP_KICK transaction:", error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createGroupBanTransaction = async (recipientAddress, adminPublicKey, groupId=694, offender, reason='Banned by admins', txGroupId = 0, fee=0.01) => {
|
const createGroupApprovalTransaction = async (recipientAddress, adminPublicKey, pendingApprovalSignature, txGroupId=694, fee=0.01) => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch account reference correctly
|
||||||
|
const accountInfo = await getAddressInfo(recipientAddress)
|
||||||
|
const accountReference = accountInfo.reference
|
||||||
|
|
||||||
|
// Validate inputs before making the request
|
||||||
|
if (!adminPublicKey || !accountReference || !recipientAddress) {
|
||||||
|
throw new Error("Missing required parameters for group invite transaction.")
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
reference: accountReference,
|
||||||
|
fee,
|
||||||
|
txGroupId,
|
||||||
|
recipient: null,
|
||||||
|
adminPublicKey,
|
||||||
|
pendingApprovalSignature,
|
||||||
|
approval: true
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Sending GROUP_APPROVAL transaction payload:", payload)
|
||||||
|
|
||||||
|
const response = await fetch(`${baseUrl}/groups/approval`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'text/plain',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text()
|
||||||
|
throw new Error(`Failed to create transaction: ${response.status}, ${errorText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawTransaction = await response.text()
|
||||||
|
console.log("Raw unsigned transaction created:", rawTransaction)
|
||||||
|
return rawTransaction
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating GROUP_APPROVAL transaction:", error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createGroupBanTransaction = async (recipientAddress, adminPublicKey, groupId=694, offender, reason='Banned by admins', txGroupId, fee) => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch account reference correctly
|
||||||
|
const accountInfo = await getAddressInfo(recipientAddress)
|
||||||
|
const accountReference = accountInfo.reference
|
||||||
|
|
||||||
|
// Validate inputs before making the request
|
||||||
|
if (!adminPublicKey || !accountReference || !recipientAddress) {
|
||||||
|
throw new Error("Missing required parameters for group invite transaction.")
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
reference: accountReference,
|
||||||
|
fee,
|
||||||
|
txGroupId,
|
||||||
|
recipient: null,
|
||||||
|
adminPublicKey,
|
||||||
|
groupId,
|
||||||
|
offender,
|
||||||
|
reason,
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Sending GROUP_BAN transaction payload:", payload)
|
||||||
|
|
||||||
|
const response = await fetch(`${baseUrl}/groups/ban`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'text/plain',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text()
|
||||||
|
throw new Error(`Failed to create transaction: ${response.status}, ${errorText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawTransaction = await response.text()
|
||||||
|
console.log("Raw unsigned transaction created:", rawTransaction)
|
||||||
|
return rawTransaction
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating GROUP_BAN transaction:", error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createGroupJoinTransaction = async (recipientAddress, joinerPublicKey, groupId, txGroupId = 0, fee) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch account reference correctly
|
// Fetch account reference correctly
|
||||||
@ -1317,16 +1544,14 @@ const createGroupBanTransaction = async (recipientAddress, adminPublicKey, group
|
|||||||
reference: accountReference,
|
reference: accountReference,
|
||||||
fee: fee | 0.01,
|
fee: fee | 0.01,
|
||||||
txGroupId: txGroupId,
|
txGroupId: txGroupId,
|
||||||
recipient: recipientAddress,
|
recipient: null,
|
||||||
adminPublicKey: adminPublicKey,
|
joinerPublicKey,
|
||||||
groupId: groupId,
|
groupId: groupId,
|
||||||
offender: offender || recipientAddress,
|
|
||||||
reason: reason
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Sending group invite transaction payload:", payload)
|
console.log("Sending GROUP_JOIN transaction payload:", payload)
|
||||||
|
|
||||||
const response = await fetch(`${baseUrl}/groups/kick`, {
|
const response = await fetch(`${baseUrl}/groups/join`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'text/plain',
|
'Accept': 'text/plain',
|
||||||
@ -1344,7 +1569,7 @@ const createGroupBanTransaction = async (recipientAddress, adminPublicKey, group
|
|||||||
console.log("Raw unsigned transaction created:", rawTransaction)
|
console.log("Raw unsigned transaction created:", rawTransaction)
|
||||||
return rawTransaction
|
return rawTransaction
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating group invite transaction:", error)
|
console.error("Error creating GROUP_JOIN transaction:", error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1392,6 +1617,103 @@ const getLatestBlockInfo = async () => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ALL QORTAL TRANSACTION TYPES BELOW
|
||||||
|
|
||||||
|
// 'GENESIS','PAYMENT','REGISTER_NAME','UPDATE_NAME','SELL_NAME','CANCEL_SELL_NAME','BUY_NAME','CREATE_POLL',
|
||||||
|
// 'VOTE_ON_POLL','ARBITRARY','ISSUE_ASSET','TRANSFER_ASSET','CREATE_ASSET_ORDER','CANCEL_ASSET_ORDER','MULTI_PAYMENT',
|
||||||
|
// 'DEPLOY_AT','MESSAGE','CHAT','PUBLICIZE','AIRDROP','AT','CREATE_GROUP','UPDATE_GROUP','ADD_GROUP_ADMIN','REMOVE_GROUP_ADMIN',
|
||||||
|
// 'GROUP_BAN','CANCEL_GROUP_BAN','GROUP_KICK','GROUP_INVITE','CANCEL_GROUP_INVITE','JOIN_GROUP','LEAVE_GROUP','GROUP_APPROVAL',
|
||||||
|
// 'SET_GROUP','UPDATE_ASSET','ACCOUNT_FLAGS','ENABLE_FORGING','REWARD_SHARE','ACCOUNT_LEVEL','TRANSFER_PRIVS','PRESENCE'
|
||||||
|
|
||||||
|
|
||||||
|
const searchTransactions = async ({
|
||||||
|
txTypes = [],
|
||||||
|
address,
|
||||||
|
confirmationStatus = 'CONFIRMED',
|
||||||
|
limit = 20,
|
||||||
|
reverse = true,
|
||||||
|
offset = 0,
|
||||||
|
startBlock = 0,
|
||||||
|
blockLimit = 0,
|
||||||
|
txGroupId = 0,
|
||||||
|
} = {}) => {
|
||||||
|
try {
|
||||||
|
// 1) Build the query string
|
||||||
|
const queryParams = [];
|
||||||
|
|
||||||
|
// Add each txType as multiple "txType=..." params
|
||||||
|
txTypes.forEach(type => {
|
||||||
|
queryParams.push(`txType=${encodeURIComponent(type)}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// If startBlock is nonzero, push "startBlock=..."
|
||||||
|
if (startBlock) {
|
||||||
|
queryParams.push(`startBlock=${encodeURIComponent(startBlock)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If blockLimit is nonzero, push "blockLimit=..."
|
||||||
|
if (blockLimit) {
|
||||||
|
queryParams.push(`blockLimit=${encodeURIComponent(blockLimit)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If txGroupId is nonzero, push "txGroupId=..."
|
||||||
|
if (txGroupId) {
|
||||||
|
queryParams.push(`txGroupId=${encodeURIComponent(txGroupId)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address
|
||||||
|
if (address) {
|
||||||
|
queryParams.push(`address=${encodeURIComponent(address)}`);
|
||||||
|
}
|
||||||
|
// Confirmation status
|
||||||
|
if (confirmationStatus) {
|
||||||
|
queryParams.push(`confirmationStatus=${encodeURIComponent(confirmationStatus)}`);
|
||||||
|
}
|
||||||
|
// Limit (if you want to explicitly pass limit=0, consider whether to skip or not)
|
||||||
|
if (limit !== undefined) {
|
||||||
|
queryParams.push(`limit=${limit}`);
|
||||||
|
}
|
||||||
|
// Reverse
|
||||||
|
if (reverse !== undefined) {
|
||||||
|
queryParams.push(`reverse=${reverse}`);
|
||||||
|
}
|
||||||
|
// Offset
|
||||||
|
if (offset) {
|
||||||
|
queryParams.push(`offset=${offset}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = queryParams.join('&');
|
||||||
|
const url = `${baseUrl}/transactions/search?${queryString}`;
|
||||||
|
|
||||||
|
// 2) Fetch
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Accept': '*/*'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(`Failed to search transactions: HTTP ${response.status}, ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Parse JSON
|
||||||
|
const txArray = await response.json();
|
||||||
|
|
||||||
|
// Check if the response is indeed an array of transactions
|
||||||
|
if (!Array.isArray(txArray)) {
|
||||||
|
throw new Error("Expected an array of transactions, but got something else.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return txArray; // e.g. [{ type, timestamp, reference, ... }, ...]
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in searchTransactions:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user