From 1040fd4018d37099ff53acc8f23f3accb7f5a1c4 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sat, 4 Jan 2025 21:18:11 +0200 Subject: [PATCH] added sign tx qortalrequest --- package-lock.json | 12 +++ package.json | 3 +- src/App.tsx | 10 ++ .../Apps/useQortalMessageListener.tsx | 2 +- src/qortalRequests.ts | 21 ++++- src/qortalRequests/get.ts | 94 +++++++++++++++++++ 6 files changed, 139 insertions(+), 3 deletions(-) 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",