diff --git a/src/App.tsx b/src/App.tsx
index 82f495b..2a3d1b6 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -19,6 +19,7 @@ import {
DialogContent,
DialogContentText,
DialogTitle,
+ FormControlLabel,
Input,
InputLabel,
Popover,
@@ -48,6 +49,7 @@ import CloseIcon from "@mui/icons-material/Close";
import './utils/seedPhrase/RandomSentenceGenerator';
import EngineeringIcon from '@mui/icons-material/Engineering';
import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet';
+import PriorityHighIcon from '@mui/icons-material/PriorityHigh';
import {
createAccount,
generateRandomSentence,
@@ -427,6 +429,7 @@ function App() {
});
const [useLocalNode, setUseLocalNode] = useState(false);
+ const [confirmRequestRead, setConfirmRequestRead] = useState(false);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [showSeed, setShowSeed] = useState(false)
const [creationStep, setCreationStep] = useState(1)
@@ -798,6 +801,7 @@ function App() {
qortalRequestCheckbox1Ref.current =
message?.payload?.checkbox1?.value || false;
}
+ setConfirmRequestRead(false)
await showQortalRequestExtension(message?.payload);
if (qortalRequestCheckbox1Ref.current) {
@@ -2043,11 +2047,13 @@ function App() {
justifyContent: "flex-start",
paddingLeft: "22px",
boxSizing: "border-box",
+ maxWidth: '700px'
}}
>
{
setRawWallet(null);
@@ -2574,11 +2582,13 @@ function App() {
justifyContent: "flex-start",
paddingLeft: "22px",
boxSizing: "border-box",
+ maxWidth: '700px'
}}
>
{
setRawWallet(null);
@@ -2679,11 +2689,13 @@ function App() {
justifyContent: "flex-start",
paddingLeft: "22px",
boxSizing: "border-box",
+ maxWidth: '700px'
}}
>
{
if(creationStep === 2){
@@ -3205,7 +3219,7 @@ function App() {
>
{
@@ -3438,6 +3452,36 @@ function App() {
)}
+{messageQortalRequestExtension?.confirmCheckbox && (
+ setConfirmRequestRead(e.target.checked)}
+ checked={confirmRequestRead}
+ edge="start"
+ tabIndex={-1}
+ disableRipple
+ sx={{
+ "&.Mui-checked": {
+ color: "white",
+ },
+ "& .MuiSvgIcon-root": {
+ color: "white",
+ },
+ }}
+ />
+ }
+ label={
+
+
+ I have read this request
+
+
+
+ }
+ />
+ )}
+
{
+ if(messageQortalRequestExtension?.confirmCheckbox && !confirmRequestRead) return
+ onOkQortalRequestExtension("accepted")
}}
- onClick={() => onOkQortalRequestExtension("accepted")}
>
accept
diff --git a/src/background.ts b/src/background.ts
index eab57c7..20dddd1 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -2309,6 +2309,48 @@ export async function createGroup({
throw new Error(res?.message || "Transaction was not able to be processed");
return res;
}
+export async function updateGroup({
+ groupId,
+ newOwner,
+ newIsOpen,
+ newDescription,
+ newApprovalThreshold,
+ newMinimumBlockDelay,
+ newMaximumBlockDelay
+}) {
+ const wallet = await getSaveWallet();
+ const address = wallet.address0;
+ if (!address) throw new Error("Cannot find user");
+ const lastReference = await getLastRef();
+ const feeres = await getFee("UPDATE_GROUP");
+ const resKeyPair = await getKeyPair();
+ const parsedData = resKeyPair;
+ const uint8PrivateKey = Base58.decode(parsedData.privateKey);
+ const uint8PublicKey = Base58.decode(parsedData.publicKey);
+ const keyPair = {
+ privateKey: uint8PrivateKey,
+ publicKey: uint8PublicKey,
+ };
+
+ const tx = await createTransaction(23, keyPair, {
+ fee: feeres.fee,
+ _groupId: groupId,
+ newOwner,
+ newIsOpen,
+ newDescription,
+ newApprovalThreshold,
+ newMinimumBlockDelay,
+ newMaximumBlockDelay,
+ lastReference: lastReference,
+ });
+
+ const signedBytes = Base58.encode(tx.signedBytes);
+
+ const res = await processTransactionVersion2(signedBytes);
+ if (!res?.signature)
+ throw new Error(res?.message || "Transaction was not able to be processed");
+ return res;
+}
export async function inviteToGroup({ groupId, qortalAddress, inviteTime }) {
const address = await getNameOrAddress(qortalAddress);
if (!address) throw new Error("Cannot find user");
diff --git a/src/components/Apps/useQortalMessageListener.tsx b/src/components/Apps/useQortalMessageListener.tsx
index e017b16..98af42d 100644
--- a/src/components/Apps/useQortalMessageListener.tsx
+++ b/src/components/Apps/useQortalMessageListener.tsx
@@ -255,7 +255,8 @@ export const listOfAllQortalRequests = [
'GET_NODE_INFO',
'GET_NODE_STATUS',
'GET_ARRR_SYNC_STATUS',
- 'SHOW_PDF_READER'
+ 'SHOW_PDF_READER',
+ 'UPDATE_GROUP'
]
export const UIQortalRequests = [
@@ -311,7 +312,8 @@ export const UIQortalRequests = [
'GET_NODE_INFO',
'GET_NODE_STATUS',
'GET_ARRR_SYNC_STATUS',
- 'SHOW_PDF_READER'
+ 'SHOW_PDF_READER',
+ 'UPDATE_GROUP'
];
@@ -575,7 +577,7 @@ isDOMContentLoaded: false
result: null,
error: {
error: response?.error,
- message: typeof response?.error === 'string' ? response?.error : 'An error has occurred'
+ message: typeof response?.error === 'string' ? response?.error : typeof response?.message === 'string' ? response?.message : 'An error has occurred'
},
});
} else {
diff --git a/src/messaging/messagesToBackground.tsx b/src/messaging/messagesToBackground.tsx
index 0ba8f71..4eebc5e 100644
--- a/src/messaging/messagesToBackground.tsx
+++ b/src/messaging/messagesToBackground.tsx
@@ -24,7 +24,7 @@ window.addEventListener("message", (event) => {
}
});
-export const sendMessageBackground = (action, data = {}, timeout = 180000, isExtension, appInfo, skipAuth) => {
+export const sendMessageBackground = (action, data = {}, timeout = 240000, isExtension, appInfo, skipAuth) => {
return new Promise((resolve, reject) => {
const requestId = generateRequestId(); // Unique ID for each request
callbackMap.set(requestId, { resolve, reject }); // Store both resolve and reject callbacks
diff --git a/src/qortalRequests.ts b/src/qortalRequests.ts
index 1df3364..83c2186 100644
--- a/src/qortalRequests.ts
+++ b/src/qortalRequests.ts
@@ -1,6 +1,6 @@
import { gateways, getApiKeyFromStorage } from "./background";
import { listOfAllQortalRequests } from "./components/Apps/useQortalMessageListener";
-import { addForeignServer, addGroupAdminRequest, addListItems, adminAction, banFromGroupRequest, cancelGroupBanRequest, cancelGroupInviteRequest, cancelSellOrder, createAndCopyEmbedLink, createBuyOrder, createGroupRequest, createPoll, createSellOrder, decryptAESGCMRequest, decryptData, decryptDataWithSharingKey, decryptQortalGroupData, deleteHostedData, deleteListItems, deployAt, encryptData, encryptDataWithSharingKey, encryptQortalGroupData, getCrossChainServerInfo, getDaySummary, getNodeInfo, getNodeStatus, getForeignFee, getHostedData, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getUserWalletTransactions, getWalletBalance, inviteToGroupRequest, joinGroup, kickFromGroupRequest, leaveGroupRequest, openNewTab, publishMultipleQDNResources, publishQDNResource, registerNameRequest, removeForeignServer, removeGroupAdminRequest, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, signTransaction, updateForeignFee, updateNameRequest, voteOnPoll, getArrrSyncStatus } from "./qortalRequests/get";
+import { addForeignServer, addGroupAdminRequest, addListItems, adminAction, banFromGroupRequest, cancelGroupBanRequest, cancelGroupInviteRequest, cancelSellOrder, createAndCopyEmbedLink, createBuyOrder, createGroupRequest, createPoll, createSellOrder, decryptAESGCMRequest, decryptData, decryptDataWithSharingKey, decryptQortalGroupData, deleteHostedData, deleteListItems, deployAt, encryptData, encryptDataWithSharingKey, encryptQortalGroupData, getCrossChainServerInfo, getDaySummary, getNodeInfo, getNodeStatus, getForeignFee, getHostedData, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getUserWalletTransactions, getWalletBalance, inviteToGroupRequest, joinGroup, kickFromGroupRequest, leaveGroupRequest, openNewTab, publishMultipleQDNResources, publishQDNResource, registerNameRequest, removeForeignServer, removeGroupAdminRequest, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, signTransaction, updateForeignFee, updateNameRequest, voteOnPoll, getArrrSyncStatus, updateGroupRequest } from "./qortalRequests/get";
import { getData, storeData } from "./utils/chromeStorage";
import { executeEvent } from "./utils/events";
@@ -1195,6 +1195,25 @@ export const isRunningGateway = async ()=> {
}
break;
}
+ case "UPDATE_GROUP" : {
+ try {
+ const res = await updateGroupRequest(request.payload, isFromExtension)
+ event.source.postMessage({
+ requestId: request.requestId,
+ action: request.action,
+ payload: res,
+ type: "backgroundMessageResponse",
+ }, event.origin);
+ } catch (error) {
+ event.source.postMessage({
+ requestId: request.requestId,
+ action: request.action,
+ error: error?.message,
+ type: "backgroundMessageResponse",
+ }, event.origin);
+ }
+ break;
+ }
case "GET_ARRR_SYNC_STATUS": {
try {
diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts
index 9dae778..2b2607c 100644
--- a/src/qortalRequests/get.ts
+++ b/src/qortalRequests/get.ts
@@ -30,6 +30,7 @@ import {
removeAdmin,
cancelInvitationToGroup,
createGroup,
+ updateGroup,
} from "../background";
import { getNameInfo, uint8ArrayToObject } from "../backgroundFunctions/encryption";
import { showSaveFilePicker } from "../components/Apps/useQortalMessageListener";
@@ -122,7 +123,7 @@ export async function retryTransaction(fn, args, throwError, retries = MAX_RETRI
if(throwError){
throw new Error(error?.message || "Unable to process transaction")
} else {
- return null
+ throw new Error(error?.message || "Unable to process transaction")
}
}
await new Promise(res => setTimeout(res, 10000));
@@ -391,7 +392,7 @@ async function getUserPermission(payload, isFromExtension) {
responseResolvers.get(requestId)(false); // Resolve with `false` if no response
responseResolvers.delete(requestId);
}
- }, 30000); // 30-second timeout
+ }, 60000); // 30-second timeout
});
}
@@ -1349,6 +1350,7 @@ export const publishMultipleQDNResources = async (
failedPublishesIdentifiers.push({
reason: errorMsg,
identifier: resource.identifier,
+ service: resource.service,
});
continue;
}
@@ -1357,6 +1359,7 @@ export const publishMultipleQDNResources = async (
failedPublishesIdentifiers.push({
reason: errorMsg,
identifier: resource.identifier,
+ service: resource.service,
});
continue;
}
@@ -1386,6 +1389,7 @@ export const publishMultipleQDNResources = async (
failedPublishesIdentifiers.push({
reason: errorMsg,
identifier: resource.identifier,
+ service: resource.service,
});
continue;
}
@@ -1413,6 +1417,7 @@ export const publishMultipleQDNResources = async (
failedPublishesIdentifiers.push({
reason: errorMsg,
identifier: resource.identifier,
+ service: resource.service,
});
continue;
}
@@ -1439,7 +1444,7 @@ export const publishMultipleQDNResources = async (
apiVersion: 2,
withFee: true,
},
- ], false);
+ ], true);
await new Promise((res) => {
setTimeout(() => {
res();
@@ -1450,17 +1455,21 @@ export const publishMultipleQDNResources = async (
failedPublishesIdentifiers.push({
reason: errorMsg,
identifier: resource.identifier,
+ service: resource.service,
});
}
} catch (error) {
failedPublishesIdentifiers.push({
reason: error?.message || "Unknown error",
identifier: resource.identifier,
+ service: resource.service,
});
}
}
if (failedPublishesIdentifiers.length > 0) {
- const obj = {};
+ const obj = {
+ message: "Some resources have failed to publish.",
+ };
obj["error"] = {
unsuccessfulPublishes: failedPublishesIdentifiers,
};
@@ -3078,6 +3087,7 @@ export const sendCoin = async (data, isFromExtension) => {
text2: `To: ${recipient}`,
highlightedText: `${amount} ${checkCoin}`,
fee: fee,
+ confirmCheckbox: true
},
isFromExtension
);
@@ -4522,15 +4532,15 @@ export const cancelGroupInviteRequest = async (data, isFromExtension) => {
export const createGroupRequest = async (data, isFromExtension) => {
- const requiredFields = ["groupId", "qortalAddress"];
+ const requiredFields = ["groupId", "qortalAddress", "groupName", "type", "approvalThreshold", "minBlock", "maxBlock"];
const missingFields: string[] = [];
requiredFields.forEach((field) => {
- if (!data[field]) {
+ if (data[field] !== undefined && data[field] !== null) {
missingFields.push(field);
}
});
const groupName = data.groupName
- const description = data?.description
+ const description = data?.description || ""
const type = +data.type
const approvalThreshold = +data?.approvalThreshold
const minBlock = +data?.minBlock
@@ -4563,6 +4573,65 @@ export const createGroupRequest = async (data, isFromExtension) => {
}
};
+export const updateGroupRequest = async (data, isFromExtension) => {
+ const requiredFields = ["groupId", "newOwner", "type", "approvalThreshold", "minBlock", "maxBlock"];
+ const missingFields: string[] = [];
+ requiredFields.forEach((field) => {
+ if (data[field] !== undefined && data[field] !== null) {
+ missingFields.push(field);
+ }
+ });
+ const groupId = +data.groupId
+ const newOwner = data.newOwner
+ const description = data?.description || ""
+ const type = +data.type
+ const approvalThreshold = +data?.approvalThreshold
+ const minBlock = +data?.minBlock
+ const maxBlock = +data.maxBlock
+
+ let groupInfo = null;
+ try {
+ const url = await createEndpoint(`/groups/${groupId}`);
+ const response = await fetch(url);
+ if (!response.ok) throw new Error("Failed to fetch group");
+
+ groupInfo = await response.json();
+ } catch (error) {
+ const errorMsg = (error && error.message) || "Group not found";
+ throw new Error(errorMsg);
+ }
+
+ const displayInvitee = await getNameInfoForOthers(newOwner)
+
+
+ const fee = await getFee("CREATE_GROUP");
+ const resPermission = await getUserPermission(
+ {
+ text1: `Do you give this application permission to update this group?`,
+ text2: `New owner: ${displayInvitee || newOwner}`,
+ highlightedText: `Group: ${groupInfo.groupName}`,
+ fee: fee.fee,
+ },
+ isFromExtension
+ );
+ const { accepted } = resPermission;
+ if (accepted) {
+ const response = await updateGroup({
+ groupId,
+ newOwner,
+ newIsOpen: type,
+ newDescription: description,
+ newApprovalThreshold: approvalThreshold,
+ newMinimumBlockDelay: minBlock,
+ newMaximumBlockDelay: maxBlock
+ })
+ return response
+
+ } else {
+ throw new Error("User declined request");
+ }
+};
+
export const decryptAESGCMRequest = async (data, isFromExtension) => {
const requiredFields = ["encryptedData", "iv", "senderPublicKey"];
requiredFields.forEach((field) => {
diff --git a/src/transactions/UpdateGroupTransaction.ts b/src/transactions/UpdateGroupTransaction.ts
new file mode 100644
index 0000000..9d9856b
--- /dev/null
+++ b/src/transactions/UpdateGroupTransaction.ts
@@ -0,0 +1,62 @@
+// @ts-nocheck
+
+
+import { QORT_DECIMALS } from "../constants/constants";
+import TransactionBase from "./TransactionBase";
+
+export default class UpdateGroupTransaction extends TransactionBase {
+ constructor() {
+ super()
+ this.type = 23
+ }
+
+
+
+
+ set fee(fee) {
+ this._fee = fee * QORT_DECIMALS
+ this._feeBytes = this.constructor.utils.int64ToBytes(this._fee)
+ }
+ set newOwner(newOwner) {
+ this._newOwner = newOwner instanceof Uint8Array ? newOwner : this.constructor.Base58.decode(newOwner)
+ }
+ set newIsOpen(newIsOpen) {
+
+ this._rGroupType = new Uint8Array(1)
+ this._rGroupType[0] = newIsOpen
+ }
+ set newDescription(newDescription) {
+ this._rGroupDescBytes = this.constructor.utils.stringtoUTF8Array(newDescription.toLocaleLowerCase())
+ this._rGroupDescLength = this.constructor.utils.int32ToBytes(this._rGroupDescBytes.length)
+ }
+ set newApprovalThreshold(newApprovalThreshold) {
+ this._rGroupApprovalThreshold = new Uint8Array(1)
+ this._rGroupApprovalThreshold[0] = newApprovalThreshold;
+ }
+ set newMinimumBlockDelay(newMinimumBlockDelay) {
+ this._rGroupMinimumBlockDelayBytes = this.constructor.utils.int32ToBytes(newMinimumBlockDelay)
+ }
+ set newMaximumBlockDelay(newMaximumBlockDelay) {
+
+ this._rGroupMaximumBlockDelayBytes = this.constructor.utils.int32ToBytes(newMaximumBlockDelay)
+ }
+
+ set _groupId(_groupId){
+ this._groupBytes = this.constructor.utils.int32ToBytes(_groupId)
+ }
+ get params() {
+ const params = super.params
+ params.push(
+ this._groupBytes,
+ this._newOwner,
+ this._rGroupDescLength,
+ this._rGroupDescBytes,
+ this._rGroupType,
+ this._rGroupApprovalThreshold,
+ this._rGroupMinimumBlockDelayBytes,
+ this._rGroupMaximumBlockDelayBytes,
+ this._feeBytes
+ )
+ return params
+ }
+}
\ No newline at end of file
diff --git a/src/transactions/transactions.ts b/src/transactions/transactions.ts
index 62c5e6a..1f0accb 100644
--- a/src/transactions/transactions.ts
+++ b/src/transactions/transactions.ts
@@ -20,6 +20,7 @@ import DeployAtTransaction from './DeployAtTransaction.js'
import RewardShareTransaction from './RewardShareTransaction.js'
import RemoveRewardShareTransaction from './RemoveRewardShareTransaction.js'
import UpdateNameTransaction from './UpdateNameTransaction.js'
+import UpdateGroupTransaction from './UpdateGroupTransaction.js'
export const transactionTypes = {
@@ -32,6 +33,7 @@ export const transactionTypes = {
18: ChatTransaction,
181: GroupChatTransaction,
22: CreateGroupTransaction,
+ 23: UpdateGroupTransaction,
24: AddGroupAdminTransaction,
25: RemoveGroupAdminTransaction,
26: GroupBanTransaction,