Many new features to handle all GROUP_APPROVAL transactions, creation and approval. All tested on the DevNet in versiuons 0.74-0.83, as such this is 0.84. See details in the General Room of the forum on page 15. #2

Merged
Quick_Mythril merged 1 commits from crowetic/Q-Mintership-Alpha:main into main 2025-01-14 01:55:00 +00:00
4 changed files with 316 additions and 142 deletions

View File

@ -445,7 +445,13 @@ const validateMinterName = async(minterName) => {
try {
const nameInfo = await getNameInfo(minterName)
const name = nameInfo.name
return name
if (name) {
console.log(`name information found, returning:`, name)
return name
} else {
console.warn(`no name information found, this is not a registered name: '${minterName}', Returning null`, name)
return null
}
} catch (error){
console.error(`extracting name from name info: ${minterName} failed.`, error)
return null
@ -831,13 +837,13 @@ const createRemoveButtonHtml = (name, cardIdentifier) => {
style="padding: 10px; background: rgb(134, 80, 4); color: white; border: none; cursor: pointer; border-radius: 5px;"
onmouseover="this.style.backgroundColor='rgb(47, 28, 11) '"
onmouseout="this.style.backgroundColor='rgb(134, 80, 4) '">
KICK Minter
Create KICK Tx
</button>
<button onclick="handleBanMinter('${name}')"
style="padding: 10px; background:rgb(93, 7, 7); color: white; border: none; cursor: pointer; border-radius: 5px;"
onmouseover="this.style.backgroundColor='rgb(39, 9, 9) '"
onmouseout="this.style.backgroundColor='rgb(93, 7, 7) '">
BAN Minter
Create BAN Tx
</button>
</div>
`
@ -847,18 +853,18 @@ const handleKickMinter = async (minterName) => {
try {
// Optional block check
let txGroupId = 0
const { height: currentHeight } = await getLatestBlockInfo()
const isBlockPassed = currentHeight >= GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT
// const { height: currentHeight } = await getLatestBlockInfo()
const isBlockPassed = await featureTriggerCheck()
if (isBlockPassed) {
console.log(`block height above featureTrigger Height, using group approval method...txGroupId 694`)
txGroupId = 694
}
// Get the minter address from name info
const minterInfo = await getNameInfo(minterName)
const minterAddress = minterInfo?.owner
const minterNameInfo = await getNameInfo(minterName)
const minterAddress = minterNameInfo?.owner
if (!minterAddress) {
alert(`No valid address found for minter name: ${minterName}`)
alert(`No valid address found for minter name: ${minterName}, this should NOT have happened, please report to developers...`)
return
}
@ -872,6 +878,11 @@ const handleKickMinter = async (minterName) => {
action: "SIGN_TRANSACTION",
unsignedBytes: rawKickTransaction
})
if (!signedKickTransaction) {
console.warn(`this only happens if the SIGN_TRANSACTION qortalRequest failed... are you using the legacy UI prior to this qortalRequest being added?`)
alert(`this only happens if the SIGN_TRANSACTION qortalRequest failed... are you using the legacy UI prior to this qortalRequest being added? Please talk to developers.`)
return
}
let txToProcess = signedKickTransaction
@ -894,8 +905,9 @@ const handleKickMinter = async (minterName) => {
const handleBanMinter = async (minterName) => {
try {
let txGroupId = 0
const { height: currentHeight } = await getLatestBlockInfo()
if (currentHeight <= GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT) {
// const { height: currentHeight } = await getLatestBlockInfo()
const isBlockPassed = await featureTriggerCheck()
if (!isBlockPassed) {
console.log(`block height is under the removal featureTrigger height, using txGroupId 0`)
txGroupId = 0
} else {
@ -903,11 +915,11 @@ const handleBanMinter = async (minterName) => {
txGroupId = 694
}
const minterInfo = await getNameInfo(minterName)
const minterAddress = minterInfo?.owner
const minterNameInfo = await getNameInfo(minterName)
const minterAddress = minterNameInfo?.owner
if (!minterAddress) {
alert(`No valid address found for minter name: ${minterName}`)
alert(`No valid address found for minter name: ${minterName}, this should NOT have happened, please report to developers...`)
return
}
@ -921,6 +933,11 @@ const handleBanMinter = async (minterName) => {
action: "SIGN_TRANSACTION",
unsignedBytes: rawBanTransaction
})
if (!signedBanTransaction) {
console.warn(`this only happens if the SIGN_TRANSACTION qortalRequest failed... are you using the legacy UI prior to this qortalRequest being added?`)
alert(`this only happens if the SIGN_TRANSACTION qortalRequest failed... are you using the legacy UI prior to this qortalRequest being added? Please talk to developers.`)
return
}
let txToProcess = signedBanTransaction

View File

@ -5,7 +5,8 @@ let isExistingCard = false
let existingCardData = {}
let existingCardIdentifier = {}
const MIN_ADMIN_YES_VOTES = 9;
const GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT = 99999999 //TODO update this to correct featureTrigger height when known, either that, or pull from core.
const GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT = 9999950 //TODO update this to correct featureTrigger height when known, either that, or pull from core.
let featureTriggerPassed = false
let isApproved = false
const loadMinterBoardPage = async () => {
@ -117,7 +118,7 @@ const loadMinterBoardPage = async () => {
event.preventDefault()
await publishCard()
})
await featureTriggerCheck()
await loadCards()
}
@ -514,8 +515,15 @@ const processPollData= async (pollData, minterGroupMembers, minterAdmins, creato
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)
const adminAddresses = [...minterAdminAddresses, ...groupAdminAddresses]
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
@ -1050,19 +1058,32 @@ const createInviteButtonHtml = (creator, cardIdentifier) => {
return `
<div id="invite-button-container-${cardIdentifier}" style="margin-top: 1em;">
<button onclick="handleInviteMinter('${creator}')"
style="padding: 10px; background:rgb(0, 109, 76) ; color: white; border: dotted; cursor: pointer; border-radius: 5px;"
style="padding: 10px; background:rgb(0, 109, 76) ; color: white; border: dotted; border-color: white; cursor: pointer; border-radius: 5px;"
onmouseover="this.style.backgroundColor='rgb(25, 47, 39) '"
onmouseout="this.style.backgroundColor='rgba(7, 122, 101, 0.63) '"
>
Invite Minter
Create Minter Invite
</button>
</div>
`
}
const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) => {
const featureTriggerCheck = async () => {
const latestBlockInfo = await getLatestBlockInfo()
const isBlockPassed = latestBlockInfo.height >= GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT
if (isBlockPassed) {
console.warn(`featureTrigger check (verifyFeatureTrigger) determined block has PASSED:`, isBlockPassed)
featureTriggerPassed = true
return true
} else {
console.warn(`featureTrigger check (verifyFeatureTrigger) determined block has NOT PASSED:`, isBlockPassed)
featureTriggerPassed = false
return false
}
}
const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) => {
const isBlockPassed = await featureTriggerCheck()
let minAdminCount
const minterAdmins = await fetchMinterGroupAdmins()
@ -1081,10 +1102,10 @@ const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) =>
}
if (isBlockPassed) {
const minterAddressInfo = await getNameInfo(creator)
const minterAddress = await minterAddressInfo.owner
const minterNameInfo = await getNameInfo(creator)
const minterAddress = await minterNameInfo.owner
if (userState.isMinterAdmin){
let groupApprovalHtml = await checkGroupApprovalAndCreateButton(minterAddress, cardIdentifier, 'GROUP_INVITE')
let groupApprovalHtml = await checkGroupApprovalAndCreateButton(minterAddress, cardIdentifier, "GROUP_INVITE")
if (groupApprovalHtml) {
return groupApprovalHtml
}
@ -1093,41 +1114,103 @@ const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) =>
}
}
if (adminYes >= minAdminCount && userState.isMinterAdmin) {
const inviteButtonHtml = createInviteButtonHtml(creator, cardIdentifier)
console.log(`admin votes over 9, creating invite button...`, adminYes)
if (adminYes >= minAdminCount && (userState.isMinterAdmin)) {
const inviteButtonHtml = createInviteButtonHtml(creator, cardIdentifier)
console.log(`admin votes over 9, creating invite button...`, adminYes)
return inviteButtonHtml
}
return null
}
const findPendingApprovalTxForAddress = async (address, txType, limit = 0, offset = 0) => {
// 1) Fetch all pending transactions
const pendingTxs = await searchPendingTransactions(limit, offset)
// if a txType is passed, return the results related to that type, if not, then return any pending tx of the potential types.
let relevantTypes
if (txType) {
relevantTypes = new Set([txType])
} else {
relevantTypes = new Set(["GROUP_INVITE", "GROUP_BAN", "GROUP_KICK"])
}
// Filter pending TX for relevant types
const relevantTxs = pendingTxs.filter((tx) => relevantTypes.has(tx.type))
// Further filter by whether 'address' matches the correct field
// - GROUP_INVITE => invitee
// - GROUP_BAN => offender
// - GROUP_KICK => member
// If the user passed a specific txType, only one branch might matter.
const matchedTxs = relevantTxs.filter((tx) => {
switch (tx.type) {
case "GROUP_INVITE":
return tx.invitee === address
case "GROUP_BAN":
return tx.offender === address
case "GROUP_KICK":
return tx.member === address
default:
return false
}
})
return matchedTxs // Array of matching pending transactions
}
const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transactionType) => {
const txTypes = [`${transactionType}`]
const confirmationStatus = 'CONFIRMED'
const txTypes = [transactionType]
const groupApprovalSearchResults = await searchTransactions(txTypes, address, confirmationStatus, limit, reverse, offset)
const pendingApprovals = groupApprovalSearchResults.filter(tx => tx.approvalStatus === 'PENDING')
const txSearchResults = await searchTransactions({
txTypes,
address: `${address}`,
confirmationStatus: 'CONFIRMED',
limit: 0,
reverse: true,
offset: 0,
startBlock: 1990000,
blockLimit: 0,
txGroupId: 694
})
const approvalTxType = ['GROUP_APPROVAL']
const approvalSearchResults = await searchTransactions({
txTypes: approvalTxType,
address: `${address}`,
confirmationStatus: 'CONFIRMED',
limit: 0,
reverse: true,
offset: 0,
startBlock: 1990000,
blockLimit: 0,
txGroupId: 0
})
console.warn(`transaction search results, this is for comparison to pendingApprovals search, these are not used:`,txSearchResults)
const pendingApprovals = await findPendingApprovalTxForAddress(address, transactionType)
if (pendingApprovals) {
console.warn(`pendingApprovals FOUND: ${pendingApprovals}`)
console.warn(`this is what is used for pending results... pendingApprovals FOUND:`, pendingApprovals)
}
if (pendingApprovals.length === 0) {
return
if ((pendingApprovals.length === 0) || (!pendingApprovals)) {
console.warn(`no pending approval transactions found, returning null...`)
return null
}
const existingApprovalCount = approvalSearchResults.length
const txSig = pendingApprovals[0].signature
if (transactionType === `GROUP_INVITE`){
const approvalButtonHtml = `
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
<h2 style="color:rgb(181, 214, 100);">Existing Invite Approvals: ${existingApprovalCount}</h2>
<button
style="padding: 8px; background:rgb(37, 99, 44); color: #000; border: 1px solid #333; border-radius: 5px; cursor: pointer;"
style="padding: 8px; background:rgb(37, 97, 99); color:rgb(215, 215, 215) ; border: 1px solid #333; border-color: white; 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
onmouseout="this.style.backgroundColor='rgb(37, 96, 99) '"
onclick="handleGroupApproval('${txSig}')">
Approve Invite Tx
</button>
</div>
`
@ -1138,12 +1221,13 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
const approvalButtonHtml = `
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
<h2 style="color:rgb(199, 100, 64);">Existing Kick Approvals: ${existingApprovalCount}</h2>
<button
style="padding: 8px; background:rgb(119, 91, 21); color: #000; border: 1px solid #333; border-radius: 5px; cursor: pointer;"
style="padding: 8px; background:rgb(119, 91, 21); color:rgb(201, 255, 251) ; border: 1px solid #333; border-color:rgb(102, 69, 60); 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
onclick="handleGroupApproval('${txSig}')">
Approve Kick Tx
</button>
</div>
`
@ -1154,12 +1238,13 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
const approvalButtonHtml = `
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
<h2 style="color:rgb(189, 40, 40);">Existing Ban Approvals: ${existingApprovalCount}</h2>
<button
style="padding: 8px; background:rgb(54, 7, 7); color: #000; border: 1px solid #333; border-radius: 5px; cursor: pointer;"
style="padding: 8px; background:rgb(54, 7, 7); color:rgb(201, 255, 251) ; border: 1px solid #333; border-color:rgb(204, 94, 94); 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
onclick="handleGroupApproval('${txSig}')">
Approve Ban Tx
</button>
</div>
`
@ -1167,7 +1252,7 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
}
}
const handleGroupApproval = async (address, pendingApprovalSignature) => {
const handleGroupApproval = async (pendingSignature) => {
try{
if (!userState.isMinterAdmin) {
console.warn(`non-admin attempting to sign approval!`)
@ -1175,22 +1260,14 @@ const handleGroupApproval = async (address, pendingApprovalSignature) => {
}
const fee = 0.01
const adminPublicKey = await getPublicKeyByName(userState.accountName)
const txGroupId = 694
const rawGroupApprovalTransaction = await createGroupApprovalTransaction(address, adminPublicKey, pendingApprovalSignature, txGroupId, fee)
const txGroupId = 0
const rawGroupApprovalTransaction = await createGroupApprovalTransaction(adminPublicKey, pendingSignature, 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)
// }
let txToProcess = signedGroupApprovalTransaction
const processGroupApprovalTx = await processTransaction(txToProcess)
if (processGroupApprovalTx) {
@ -1209,23 +1286,24 @@ const handleJoinGroup = async (minterAddress) => {
try{
if (userState.accountAddress === minterAddress) {
console.log(`minter user found `)
const qRequestAttempt = await qortalRequest({
action: "JOIN_GROUP",
groupId: 694
})
if (qRequestAttempt) {
return true
}
const joinerPublicKey = getPublicKeyFromAddress(minterAddress)
fee = 0.01
const 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){
@ -1295,16 +1373,16 @@ const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCoun
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;"
style="padding: 8px; background:rgb(37, 99, 44); color:rgb(240, 240, 240); border: 1px solid rgb(255, 255, 255); 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
Join MINTER Group
</button>
</div>
`
}else{
console.log(`user is not the minter... displaying no join button`)
console.log(`user is not the minter... NOT displaying any join button`)
inviteHtmlAdd = ''
}
}

View File

@ -267,35 +267,35 @@ const verifyUserIsAdmin = async () => {
console.log('userGroups:', userGroups)
const minterGroupAdmins = await fetchMinterGroupAdmins()
console.log('minterGroupAdmins.members:', minterGroupAdmins)
console.log('minterGroupAdmins:', minterGroupAdmins)
if (!Array.isArray(userGroups)) {
throw new Error('userGroups is not an array or is undefined')
}
if (!Array.isArray(minterGroupAdmins)) {
throw new Error('minterGroupAdmins.members is not an array or is undefined')
throw new Error('minterGroupAdmins is not an array or is undefined')
}
const isAdmin = userGroups.some(group => adminGroups.includes(group.groupName))
const isMinterAdmin = minterGroupAdmins.some(admin => admin.member === userState.accountAddress && admin.isAdmin)
if (isMinterAdmin) {
userState.isMinterAdmin = true
userState.isMinterAdmin = isMinterAdmin
userState.isAdmin = isMinterAdmin || isAdmin
userState.isForumAdmin = isAdmin
if ((userState.isAdmin) || (userState.isMinterAdmin || userState.isForumAdmin)){
console.log(`user is one of the following: admin: ${userState.isAdmin} - minterAdmin: ${userState.isMinterAdmin} - forumAdmin: ${userState.isForumAdmin}`)
return userState.isAdmin
} else {
return false
}
if (isAdmin) {
userState.isAdmin = true
userState.isForumAdmin = true
}
return userState.isAdmin
} catch (error) {
console.error('Error verifying user admin status:', error)
throw error
}
}
const verifyAddressIsAdmin = async (address) => {
console.log('verifyAddressIsAdmin called')
console.log('address:', address)
@ -307,7 +307,7 @@ const verifyAddressIsAdmin = async (address) => {
const userGroups = await getUserGroups(address)
const minterGroupAdmins = await fetchMinterGroupAdmins()
const isAdmin = await userGroups.some(group => adminGroups.includes(group.groupName))
const isMinterAdmin = minterGroupAdmins.members.some(admin => admin.member === address && admin.isAdmin)
const isMinterAdmin = minterGroupAdmins.some(admin => admin.member === address && admin.isAdmin)
if ((isMinterAdmin) || (isAdmin)) {
return true
} else {
@ -483,27 +483,61 @@ const fetchMinterGroupAdmins = async () => {
//use what is returned .member to obtain each member... {"member": "memberAddress", "isAdmin": "true"}
}
const fetchAllAdminGroupsMembers = async () => {
try {
let adminGroupMemberAddresses = [] // Declare outside loop to accumulate results
for (const groupID of adminGroupIDs) {
const response = await fetch(`${baseUrl}/groups/members/${groupID}?limit=0`, {
method: 'GET',
headers: { 'Accept': 'application/json' },
})
// const fetchAllAdminGroupsMembers = async () => {
// try {
// let adminGroupMemberAddresses = [] // Declare outside loop to accumulate results
// for (const groupID of adminGroupIDs) {
// const response = await fetch(`${baseUrl}/groups/members/${groupID}?limit=0`, {
// method: 'GET',
// headers: { 'Accept': 'application/json' },
// })
const groupData = await response.json()
if (groupData.members && Array.isArray(groupData.members)) {
adminGroupMemberAddresses.push(...groupData.members) // Merge members into the array
} else {
console.warn(`Group ${groupID} did not return valid members.`)
// const groupData = await response.json()
// if (groupData.members && Array.isArray(groupData.members)) {
// adminGroupMemberAddresses.push(...groupData.members) // Merge members into the array
// } else {
// console.warn(`Group ${groupID} did not return valid members.`)
// }
// }
// return adminGroupMemberAddresses
// } catch (error) {
// console.log('Error fetching admin group members', error)
// }
// }
const fetchAllAdminGroupsMembers = async () => {
try {
// We'll track addresses so we don't duplicate the same .member
const seenAddresses = new Set()
const resultObjects = []
for (const groupID of adminGroupIDs) {
const response = await fetch(`${baseUrl}/groups/members/${groupID}?limit=0`, {
method: 'GET',
headers: { Accept: 'application/json' },
})
const groupData = await response.json()
if (Array.isArray(groupData?.members)) {
for (const memberObj of groupData.members) {
if (memberObj?.member && !seenAddresses.has(memberObj.member)) {
// Add to final results
resultObjects.push(memberObj)
// Mark address as seen
seenAddresses.add(memberObj.member)
}
}
} else {
console.warn(`Group ${groupID} did not return valid members.`)
}
return adminGroupMemberAddresses
}
return resultObjects // array of objects e.g. [{member, joined}, ...]
} catch (error) {
console.log('Error fetching admin group members', error)
console.error('Error fetching admin group members', error)
return []
}
}
}
const fetchMinterGroupMembers = async () => {
try {
@ -548,27 +582,21 @@ const fetchAllGroups = async (limit) => {
const fetchAdminGroupsMembersPublicKeys = async () => {
try {
let adminGroupMemberAddresses = [] // Declare outside loop to accumulate results
for (const groupID of adminGroupIDs) {
const response = await fetch(`${baseUrl}/groups/members/${groupID}?limit=0`, {
method: 'GET',
headers: { 'Accept': 'application/json' },
})
const groupData = await response.json()
if (groupData.members && Array.isArray(groupData.members)) {
adminGroupMemberAddresses.push(...groupData.members) // Merge members into the array
} else {
console.warn(`Group ${groupID} did not return valid members.`)
}
}
let adminGroupMemberAddresses = await fetchAllAdminGroupsMembers()
let minterAdminMemberAddresses = await fetchMinterGroupAdmins()
// Check if adminGroupMemberAddresses has valid data
if (!Array.isArray(adminGroupMemberAddresses)) {
throw new Error("Expected 'adminGroupMemberAddresses' to be an array but got a different structure")
}
let allMemberPublicKeys = [] // Declare outside loop to accumulate results
if (Array.isArray(adminGroupMemberAddresses)) {
console.log(`adding + minterAdminMemberAddresses:`, minterAdminMemberAddresses)
adminGroupMemberAddresses.push(...minterAdminMemberAddresses)
console.log(`final = all adminGroupMemberAddresses`, adminGroupMemberAddresses)
}
let allMemberPublicKeys = []
for (const member of adminGroupMemberAddresses) {
const memberPublicKey = await getPublicKeyFromAddress(member.member)
allMemberPublicKeys.push(memberPublicKey)
@ -1430,15 +1458,16 @@ const createGroupKickTransaction = async (recipientAddress, adminPublicKey, grou
}
}
const createGroupApprovalTransaction = async (recipientAddress, adminPublicKey, pendingApprovalSignature, txGroupId=694, fee=0.01) => {
const createGroupApprovalTransaction = async (adminPublicKey, pendingSignature, txGroupId=0, fee=0.01) => {
try {
// Fetch account reference correctly
const accountInfo = await getAddressInfo(recipientAddress)
const accountReference = accountInfo.reference
const adminAddress = await getAddressFromPublicKey(adminPublicKey)
const addressInfo = await getAddressInfo(adminAddress)
const accountReference = addressInfo.reference
// Validate inputs before making the request
if (!adminPublicKey || !accountReference || !recipientAddress) {
if (!adminPublicKey || !accountReference ) {
throw new Error("Missing required parameters for group invite transaction.")
}
@ -1447,9 +1476,8 @@ const createGroupApprovalTransaction = async (recipientAddress, adminPublicKey,
reference: accountReference,
fee,
txGroupId,
recipient: null,
adminPublicKey,
pendingApprovalSignature,
pendingSignature,
approval: true
}
@ -1494,8 +1522,7 @@ const createGroupBanTransaction = async (recipientAddress, adminPublicKey, group
timestamp: Date.now(),
reference: accountReference,
fee,
txGroupId,
recipient: null,
txGroupId,
adminPublicKey,
groupId,
offender,
@ -1535,18 +1562,17 @@ const createGroupJoinTransaction = async (recipientAddress, joinerPublicKey, gro
const accountReference = accountInfo.reference
// Validate inputs before making the request
if (!adminPublicKey || !accountReference || !recipientAddress) {
if (!accountReference || !recipientAddress) {
throw new Error("Missing required parameters for group invite transaction.")
}
const payload = {
timestamp: Date.now(),
reference: accountReference,
fee: fee | 0.01,
txGroupId: txGroupId,
recipient: null,
fee: fee,
txGroupId,
joinerPublicKey,
groupId: groupId,
groupId
}
console.log("Sending GROUP_JOIN transaction payload:", payload)
@ -1639,35 +1665,35 @@ const searchTransactions = async ({
} = {}) => {
try {
// 1) Build the query string
const queryParams = [];
const queryParams = []
// Add each txType as multiple "txType=..." params
txTypes.forEach(type => {
queryParams.push(`txType=${encodeURIComponent(type)}`);
});
queryParams.push(`txType=${encodeURIComponent(type)}`)
})
// If startBlock is nonzero, push "startBlock=..."
if (startBlock) {
queryParams.push(`startBlock=${encodeURIComponent(startBlock)}`);
queryParams.push(`startBlock=${encodeURIComponent(startBlock)}`)
}
// If blockLimit is nonzero, push "blockLimit=..."
if (blockLimit) {
queryParams.push(`blockLimit=${encodeURIComponent(blockLimit)}`);
queryParams.push(`blockLimit=${encodeURIComponent(blockLimit)}`)
}
// If txGroupId is nonzero, push "txGroupId=..."
if (txGroupId) {
queryParams.push(`txGroupId=${encodeURIComponent(txGroupId)}`);
queryParams.push(`txGroupId=${encodeURIComponent(txGroupId)}`)
}
// Address
if (address) {
queryParams.push(`address=${encodeURIComponent(address)}`);
queryParams.push(`address=${encodeURIComponent(address)}`)
}
// Confirmation status
if (confirmationStatus) {
queryParams.push(`confirmationStatus=${encodeURIComponent(confirmationStatus)}`);
queryParams.push(`confirmationStatus=${encodeURIComponent(confirmationStatus)}`)
}
// Limit (if you want to explicitly pass limit=0, consider whether to skip or not)
if (limit !== undefined) {
@ -1684,6 +1710,7 @@ const searchTransactions = async ({
const queryString = queryParams.join('&');
const url = `${baseUrl}/transactions/search?${queryString}`;
console.warn(`calling the following for search transactions: ${url}`)
// 2) Fetch
const response = await fetch(url, {
@ -1691,15 +1718,15 @@ const searchTransactions = async ({
headers: {
'Accept': '*/*'
}
});
})
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to search transactions: HTTP ${response.status}, ${errorText}`);
const errorText = await response.text()
throw new Error(`Failed to search transactions: HTTP ${response.status}, ${errorText}`)
}
// 3) Parse JSON
const txArray = await response.json();
const txArray = await response.json()
// Check if the response is indeed an array of transactions
if (!Array.isArray(txArray)) {
@ -1708,10 +1735,42 @@ const searchTransactions = async ({
return txArray; // e.g. [{ type, timestamp, reference, ... }, ...]
} catch (error) {
console.error("Error in searchTransactions:", error);
throw error;
console.error("Error in searchTransactions:", error)
throw error
}
};
}
const searchPendingTransactions = async (limit = 20, offset = 0) => {
try {
const queryParams = []
if (limit) queryParams.push(`limit=${limit}`)
if (offset) queryParams.push(`offset=${offset}`)
const queryString = queryParams.join('&');
const url = `${baseUrl}/transactions/pending${queryString ? `?${queryString}` : ''}`
const response = await fetch(url, {
method: 'GET',
headers: { 'Accept': '*/*' },
})
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to search pending transactions: HTTP ${response.status}, ${errorText}`)
}
const result = await response.json();
if (!Array.isArray(result)) {
throw new Error("Expected an array for pending transactions, but got something else.")
}
return result; // e.g. [{type, signature, approvalStatus, ...}, ...]
} catch (error) {
console.error("Error in searchPendingTransactions:", error)
throw error
}
}

View File

@ -45,7 +45,7 @@
</a>
</span>
<span class="navbar-caption-wrap">
<a class="navbar-caption display-4" href="index.html">Q-Mintership Alpha
<a class="navbar-caption display-4" href="index.html">Q-Mintership-Alpha (v0.84b)
</a>
</span>
</div>
@ -64,7 +64,7 @@
<img src="assets/images/again-edited-qortal-minting-icon-156x156.png" alt="">
</a>
</span>
<span class="navbar-caption-wrap"><a class="navbar-caption text-primary display-4" href="index.html">Q-Mintership Alpha v0.72b<br></a></span>
<span class="navbar-caption-wrap"><a class="navbar-caption text-primary display-4" href="index.html">Q-Mintership Alpha v0.84b<br></a></span>
</div>
<ul class="navbar-nav nav-dropdown" data-app-modern-menu="true"><li class="nav-item"><a class="nav-link link text-primary display-7" href="MINTERSHIP-FORUM"></a></li></ul>
@ -169,6 +169,26 @@
<section data-bs-version="5.1" class="content7 boldm5 cid-uufIRKtXOO" id="content7-6">
<div class="container">
<div class="row">
<div class="col-12 col-lg-7 card">
<div class="title-wrapper">
<h2 class="mbr-section-title mbr-fonts-style display-2">
v0.84beta 01-13-2025</h2>
</div>
</div>
<div class="col-12 col-lg-5 card">
<div class="text-wrapper">
<p class="mbr-text mbr-fonts-style display-7">
<b><u>NEW Features</u></b>- <b>All GROUP_APPROVAL and Transaction functionality</b> - Ability to CREATE INVITE, KICK, AND BAN transactions, and functionality to allow GROUP_APPROVAL of said transactions. Checks for the featureTrigger and whether it has passed or not, to change the functionality to the new methods post-core-update. See page 15 of the General Room for details - <a href="MINTERSHIP-FORUM">FORUM</a> </p><p></p>
<p class="mbr-text mbr-fonts-style display-7"><b><u>JOIN MINTER GROUP</u></b> - <b>Join the Minter Group from your Minter Card</b> - After the minter who published a card is APPROVED (with GROUP_APPROVAL after next core update, or by an invite from crowetic prior, after 40%+ approval by Minter Admins...) a 'JOIN MINTER GROUP' button will appear on the card for the user that published it. Upon clicking this button a JOIN_GROUP transaction will be created, and processed, thus accepting the invite to the MINTER group. </p><p></p>
<p class="mbr-text mbr-fonts-style display-7"><b><u>CHECK THE ANNOUNCEMENTS</b></u> - <b>in the <a href="MINTERSHIP-FORUM">FORUM</a> </b> </p><p>on the 15th page of General room, and other related information in the MINTER ROOM on page 5.</p>
<p class="mbr-text mbr-fonts-style display-7"><b>Various additional fixes and cleanup</b>. All of the above functionality has been TESTED on the DevNet, that is why there is a big jump in VERSION from the last to this one.</p><p>Many additional features are coming soon, including a NOTIFICATION SYSTEM, PROFILES AND EXPLORER, and MUCH MORE. Q-Mintership will become more than simply 'the app used to become a minter'. </p>
</div>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-12 col-lg-7 card">
@ -458,12 +478,12 @@
<div class="title-wrapper">
<div class="title-wrap">
<img src="assets/images/again-edited-qortal-minting-icon-156x156.png" alt="">
<h2 class="mbr-section-title mbr-fonts-style display-5">Q-Mintership Alpha</h2>
<h2 class="mbr-section-title mbr-fonts-style display-5">Q-Mintership-Alpha</h2>
</div>
</div>
<a class="link-wrap" href="#">
<p class="mbr-link mbr-fonts-style display-4">Q-Mintership v0.72beta</p>
<p class="mbr-link mbr-fonts-style display-4">Q-Mintership-Alpha v0.84beta</p>
</a>
</div>
<div class="col-12 col-lg-6">