diff --git a/package-lock.json b/package-lock.json
index 3efb05f..f71eea8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -68,6 +68,7 @@
"react-frame-component": "^5.2.7",
"react-infinite-scroller": "^1.2.6",
"react-intersection-observer": "^9.13.0",
+ "react-json-view-lite": "^2.0.1",
"react-loader-spinner": "^6.1.6",
"react-qr-code": "^2.0.15",
"react-quick-pinch-zoom": "^5.1.0",
@@ -11999,6 +12000,17 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/react-json-view-lite": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-2.1.0.tgz",
+ "integrity": "sha512-4JdlXC+dWPRXPL4fK/NsK6W103+mmpXDeWCHJGhgjPSvuyHnpwQUJ+ClUj4MFlLb4cPxi3T0/PW414JlTKMCJg==",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0"
+ }
+ },
"node_modules/react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
diff --git a/package.json b/package.json
index 83cf662..1b32d33 100644
--- a/package.json
+++ b/package.json
@@ -87,7 +87,8 @@
"tiptap-extension-resize-image": "^1.1.8",
"ts-key-enum": "^2.0.12",
"vite-plugin-top-level-await": "^1.4.4",
- "vite-plugin-wasm": "^3.3.0"
+ "vite-plugin-wasm": "^3.3.0",
+ "react-json-view-lite": "^2.0.1"
},
"devDependencies": {
"@testing-library/dom": "^10.3.0",
diff --git a/src/App.tsx b/src/App.tsx
index 6dff4f0..97e440a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -27,6 +27,8 @@ import {
Typography,
} from "@mui/material";
import { decryptStoredWallet } from "./utils/decryptWallet";
+import { JsonView, allExpanded, darkStyles } from 'react-json-view-lite';
+import 'react-json-view-lite/dist/index.css';
import { CountdownCircleTimer } from "react-countdown-circle-timer";
import Logo1 from "./assets/svgs/Logo1.svg";
import Logo1Dark from "./assets/svgs/Logo1Dark.svg";
@@ -3163,7 +3165,15 @@ await showInfo({
>
{messageQortalRequestExtension?.highlightedText}
+ {messageQortalRequestExtension?.json && (
+ <>
+
+
+
+
+ >
+ )}
{messageQortalRequestExtension?.fee && (
<>
diff --git a/src/components/Apps/useQortalMessageListener.tsx b/src/components/Apps/useQortalMessageListener.tsx
index e465d15..83bd708 100644
--- a/src/components/Apps/useQortalMessageListener.tsx
+++ b/src/components/Apps/useQortalMessageListener.tsx
@@ -183,7 +183,7 @@ const UIQortalRequests = [
'GET_TX_ACTIVITY_SUMMARY', 'GET_FOREIGN_FEE', 'UPDATE_FOREIGN_FEE',
'GET_SERVER_CONNECTION_HISTORY', 'SET_CURRENT_FOREIGN_SERVER',
'ADD_FOREIGN_SERVER', 'REMOVE_FOREIGN_SERVER', 'GET_DAY_SUMMARY', 'CREATE_TRADE_BUY_ORDER',
- 'CREATE_TRADE_SELL_ORDER', 'CANCEL_TRADE_SELL_ORDER', 'IS_USING_GATEWAY', 'ADMIN_ACTION', 'OPEN_NEW_TAB', 'CREATE_AND_COPY_EMBED_LINK', 'DECRYPT_QORTAL_GROUP_DATA', 'DECRYPT_DATA_WITH_SHARING_KEY', 'DELETE_HOSTED_DATA', 'GET_HOSTED_DATA'
+ 'CREATE_TRADE_SELL_ORDER', 'CANCEL_TRADE_SELL_ORDER', 'IS_USING_GATEWAY', 'SIGN_TRANSACTION', 'ADMIN_ACTION', 'OPEN_NEW_TAB', 'CREATE_AND_COPY_EMBED_LINK', 'DECRYPT_QORTAL_GROUP_DATA', 'DECRYPT_DATA_WITH_SHARING_KEY', 'DELETE_HOSTED_DATA', 'GET_HOSTED_DATA'
];
diff --git a/src/qortalRequests.ts b/src/qortalRequests.ts
index 5fa8088..723b20e 100644
--- a/src/qortalRequests.ts
+++ b/src/qortalRequests.ts
@@ -1,5 +1,5 @@
import { gateways, getApiKeyFromStorage } from "./background";
-import { addForeignServer, addListItems, adminAction, cancelSellOrder, createAndCopyEmbedLink, createBuyOrder, createPoll, decryptData, decryptDataWithSharingKey, decryptQortalGroupData, deleteHostedData, deleteListItems, deployAt, encryptData, encryptDataWithSharingKey, encryptQortalGroupData, getCrossChainServerInfo, getDaySummary, getForeignFee, getHostedData, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getWalletBalance, joinGroup, openNewTab, publishMultipleQDNResources, publishQDNResource, removeForeignServer, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, updateForeignFee, voteOnPoll } from "./qortalRequests/get";
+import { addForeignServer, addListItems, adminAction, cancelSellOrder, createAndCopyEmbedLink, createBuyOrder, createPoll, decryptData, decryptDataWithSharingKey, decryptQortalGroupData, deleteHostedData, deleteListItems, deployAt, encryptData, encryptDataWithSharingKey, encryptQortalGroupData, getCrossChainServerInfo, getDaySummary, getForeignFee, getHostedData, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getWalletBalance, joinGroup, openNewTab, publishMultipleQDNResources, publishQDNResource, removeForeignServer, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, signTransaction, updateForeignFee, voteOnPoll } from "./qortalRequests/get";
import { getData, storeData } from "./utils/chromeStorage";
@@ -674,6 +674,25 @@ export const isRunningGateway = async ()=> {
}
break;
}
+ case "SIGN_TRANSACTION": {
+ try {
+ const res = await signTransaction(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 "OPEN_NEW_TAB": {
try {
diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts
index 48b6080..46477fe 100644
--- a/src/qortalRequests/get.ts
+++ b/src/qortalRequests/get.ts
@@ -45,6 +45,8 @@ import { executeEvent } from "../utils/events";
import { extractComponents } from "../components/Chat/MessageDisplay";
import { decryptResource, getGroupAdmins, getPublishesFromAdmins, validateSecretKey } from "../components/Group/Group";
import { getPublishesFromAdminsAdminSpace } from "../components/Chat/AdminSpaceInner";
+import nacl from "../deps/nacl-fast";
+import utils from "../utils/utils";
const btcFeePerByte = 0.00000100
const ltcFeePerByte = 0.00000030
@@ -3210,6 +3212,98 @@ export const adminAction = async (data, isFromExtension) => {
}
};
+export const signTransaction = async (data, isFromExtension) => {
+ const requiredFields = ["unsignedBytes"];
+ const missingFields: string[] = [];
+ requiredFields.forEach((field) => {
+ if (!data[field]) {
+ missingFields.push(field);
+ }
+ });
+ if (missingFields.length > 0) {
+ const missingFieldsString = missingFields.join(", ");
+ const errorMsg = `Missing fields: ${missingFieldsString}`;
+ throw new Error(errorMsg);
+ }
+
+ const shouldProcess = data?.process || false;
+ const _url = await createEndpoint(
+ "/transactions/decode?ignoreValidityChecks=false"
+ );
+
+ const _body = data.unsignedBytes;
+ const response = await fetch(_url, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: _body,
+ });
+ if (!response.ok) throw new Error("Failed to decode transaction");
+ const decodedData = await response.json();
+ const resPermission = await getUserPermission(
+ {
+ text1: `Do you give this application permission to ${ shouldProcess ? 'SIGN and PROCESS' : 'SIGN' } a transaction?`,
+ highlightedText: "Read the transaction carefully before accepting!",
+ text2: `Tx type: ${decodedData.type}`,
+ json: decodedData,
+ },
+ isFromExtension
+ );
+ const { accepted } = resPermission;
+ if (accepted) {
+
+ const urlConverted = await createEndpoint("/transactions/convert");
+
+ const responseConverted = await fetch(urlConverted, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: data.unsignedBytes,
+ });
+ 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 convertedBytes = await responseConverted.text();
+ const txBytes = Base58.decode(data.unsignedBytes);
+ const _arbitraryBytesBuffer = Object.keys(txBytes).map(function (key) {
+ return txBytes[key];
+ });
+ const arbitraryBytesBuffer = new Uint8Array(_arbitraryBytesBuffer);
+ const txByteSigned = Base58.decode(convertedBytes);
+ const _bytesForSigningBuffer = Object.keys(txByteSigned).map(function (
+ key
+ ) {
+ return txByteSigned[key];
+ });
+ const bytesForSigningBuffer = new Uint8Array(_bytesForSigningBuffer);
+ const signature = nacl.sign.detached(
+ bytesForSigningBuffer,
+ keyPair.privateKey
+ );
+ const signedBytes = utils.appendBuffer(arbitraryBytesBuffer, signature);
+ const signedBytesToBase58 = Base58.encode(signedBytes);
+ if(!shouldProcess){
+ return uint8ArrayToBase64(signedBytes);
+ }
+ const res = await processTransactionVersion2(signedBytesToBase58);
+ if (!res?.signature)
+ throw new Error(
+ res?.message || "Transaction was not able to be processed"
+ );
+ return res;
+
+ } else {
+ throw new Error("User declined request");
+ }
+};
+
export const openNewTab = async (data, isFromExtension) => {
const requiredFields = [
"qortalLink",