diff --git a/src/App.tsx b/src/App.tsx index 77f32c5..ddc752d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1008,7 +1008,7 @@ function App() { resetAllRecoil(); }; - function roundUpToDecimals(number, decimals = 8) { + function roundUpToDecimals(number, decimals = 8) { const factor = Math.pow(10, decimals); // Create a factor based on the number of decimals return Math.ceil(+number * factor) / factor; } diff --git a/src/background.ts b/src/background.ts index 2eb7b5d..27826b4 100644 --- a/src/background.ts +++ b/src/background.ts @@ -14,6 +14,7 @@ import Base58 from "./deps/Base58"; import { base64ToUint8Array, decryptSingle, + encryptDataGroup, encryptSingle, objectToBase64, } from "./qdn/encryption/group-encryption"; @@ -129,19 +130,19 @@ function handleNotificationClick(notificationId) { const match = id.match(new RegExp(`${key}=([^_]+)`)); return match ? match[1] : null; } - + const targetOrigin = window.location.origin; // Handle specific notification types and post the message accordingly if (isDirect) { const fromValue = getParameterValue(decodedNotificationId, "_from"); window.postMessage( { action: "NOTIFICATION_OPEN_DIRECT", payload: { from: fromValue } }, - "*" + targetOrigin ); } else if (isGroup) { const fromValue = getParameterValue(decodedNotificationId, "_from"); window.postMessage( { action: "NOTIFICATION_OPEN_GROUP", payload: { from: fromValue } }, - "*" + targetOrigin ); } else if (isGroupAnnouncement) { const fromValue = getParameterValue(decodedNotificationId, "_from"); @@ -150,18 +151,19 @@ function handleNotificationClick(notificationId) { action: "NOTIFICATION_OPEN_ANNOUNCEMENT_GROUP", payload: { from: fromValue }, }, - "*" + targetOrigin ); } else if (isNewThreadPost) { const dataValue = getParameterValue(decodedNotificationId, "_data"); try { + const targetOrigin = window.location.origin; const dataParsed = JSON.parse(dataValue); window.postMessage( { action: "NOTIFICATION_OPEN_THREAD_NEW_POST", payload: { data: dataParsed }, }, - "*" + targetOrigin ); } catch (error) { console.error("Error parsing JSON data for thread post notification:", error); @@ -371,9 +373,9 @@ async function checkWebviewFocus() { const timeout = setTimeout(() => { resolve(false); // No response within 1 second, assume not focused }, 1000); - + const targetOrigin = window.location.origin; // Send a message to check focus - window.postMessage({ action: "CHECK_FOCUS" }, "*"); + window.postMessage({ action: "CHECK_FOCUS" }, targetOrigin); // Listen for the response const handleMessage = (event) => { @@ -1324,19 +1326,20 @@ const getStoredData = async (key) => { export async function handleActiveGroupDataFromSocket({ groups, directs }) { try { + const targetOrigin = window.location.origin; window.postMessage( { action: "SET_GROUPS", payload: groups, }, - "*" + targetOrigin ); window.postMessage( { action: "SET_DIRECTS", payload: directs, }, - "*" + targetOrigin ); groups = groups; @@ -1357,7 +1360,7 @@ export async function handleActiveGroupDataFromSocket({ groups, directs }) { } catch (error) {} } -async function sendChat({ qortAddress, recipientPublicKey, message }) { +async function sendChatForBuyOrder({ qortAddress, recipientPublicKey, message }) { let _reference = new Uint8Array(64); self.crypto.getRandomValues(_reference); @@ -1398,13 +1401,7 @@ async function sendChat({ qortAddress, recipientPublicKey, message }) { isText: 1, }); if (!hasEnoughBalance) { - const _encryptedMessage = tx._encryptedMessage; - const encryptedMessageToBase58 = Base58.encode(_encryptedMessage); - return { - encryptedMessageToBase58, - signature: "id-" + Date.now() + "-" + Math.floor(Math.random() * 1000), - reference, - }; + throw new Error('You must have at least 4 QORT to trade using the gateway.') } const path = `${import.meta.env.BASE_URL}memory-pow.wasm.full`; @@ -1635,7 +1632,7 @@ export async function decryptDirectFunc({ messages, involvingAddress }) { return holdMessages; } -async function createBuyOrderTx({ crosschainAtInfo, useLocal }) { +export async function createBuyOrderTx({ crosschainAtInfo, useLocal }) { try { if (useLocal) { const wallet = await getSaveWallet(); @@ -1645,7 +1642,7 @@ async function createBuyOrderTx({ crosschainAtInfo, useLocal }) { const resKeyPair = await getKeyPair(); const parsedData = resKeyPair; const message = { - addresses: crosschainAtInfo.map((order) => order.qortalAtAddress), + addresses: crosschainAtInfo.map((order)=> order.qortalAtAddress), foreignKey: parsedData.ltcPrivateKey, receivingAddress: address, }; @@ -1655,7 +1652,7 @@ async function createBuyOrderTx({ crosschainAtInfo, useLocal }) { ); const apiKey = await getApiKeyFromStorage(); const responseFetch = await fetch( - `${apiKey?.url}/crosschain/tradebot/respondmultiple?apiKey=${apiKey?.apikey}`, + `http://127.0.0.1:12391/crosschain/tradebot/respondmultiple?apiKey=${apiKey?.apikey}`, { method: "POST", headers: { @@ -1682,7 +1679,9 @@ async function createBuyOrderTx({ crosschainAtInfo, useLocal }) { callResponse: response, extra: { message: "Transaction processed successfully!", - atAddresses: crosschainAtInfo.map((order) => order.qortalAtAddress), + atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress), + senderAddress: address, + node: 'http://127.0.0.1:12391' }, }; } else { @@ -1690,23 +1689,14 @@ async function createBuyOrderTx({ crosschainAtInfo, useLocal }) { callResponse: "ERROR", extra: { message: response, - atAddresses: crosschainAtInfo.map((order) => order.qortalAtAddress), + atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress), + senderAddress: address, + node: 'http://127.0.0.1:12391' }, }; } - // setTimeout(() => { - // chrome.tabs.query({}, function (tabs) { - // tabs.forEach((tab) => { - // chrome.tabs.sendMessage(tab.id, { - // type: "RESPONSE_FOR_TRADES", - // message: responseMessage, - // }); - // }); - // }); - // }, 5000); - - return; + return responseMessage } const wallet = await getSaveWallet(); const address = wallet.address0; @@ -1714,40 +1704,56 @@ async function createBuyOrderTx({ crosschainAtInfo, useLocal }) { const resKeyPair = await getKeyPair(); const parsedData = resKeyPair; const message = { - addresses: crosschainAtInfo.map((order) => order.qortalAtAddress), + addresses: crosschainAtInfo.map((order)=> order.qortalAtAddress), foreignKey: parsedData.ltcPrivateKey, receivingAddress: address, }; - const res = await sendChat({ + const res = await sendChatForBuyOrder({ qortAddress: proxyAccountAddress, recipientPublicKey: proxyAccountPublicKey, message, }); if (res?.signature) { - listenForChatMessageForBuyOrder({ + let responseMessage; + + const message = await listenForChatMessageForBuyOrder({ nodeBaseUrl: buyTradeNodeBaseUrl, senderAddress: proxyAccountAddress, senderPublicKey: proxyAccountPublicKey, signature: res?.signature, }); - if (res?.encryptedMessageToBase58) { - return { - atAddresses: crosschainAtInfo.map((order) => order.qortalAtAddress), - encryptedMessageToBase58: res?.encryptedMessageToBase58, - node: buyTradeNodeBaseUrl, - qortAddress: address, - chatSignature: res?.signature, - senderPublicKey: parsedData.publicKey, - sender: address, - reference: res?.reference, - }; + // const status = response.callResponse === true ? 'trade-ongoing' : 'trade-failed' + // if (res?.encryptedMessageToBase58) { + // return { + // atAddresses: crosschainAtInfo.map((order) => order.qortalAtAddress), + // encryptedMessageToBase58: res?.encryptedMessageToBase58, + // node: buyTradeNodeBaseUrl, + // qortAddress: address, + // chatSignature: res?.signature, + // senderPublicKey: parsedData.publicKey, + // sender: address, + // reference: res?.reference, + // response.callResponse + // }; + // } + // return { + // atAddresses: crosschainAtInfo.map((order) => order.qortalAtAddress), + // chatSignature: res?.signature, + // node: buyTradeNodeBaseUrl, + // qortAddress: address, + // }; + responseMessage = { + callResponse: message.callResponse, + extra: { + message: message?.extra?.message, + senderAddress: address, + node: buyTradeNodeBaseUrl, + atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress), + }, + encryptedMessageToBase58 } - return { - atAddresses: crosschainAtInfo.map((order) => order.qortalAtAddress), - chatSignature: res?.signature, - node: buyTradeNodeBaseUrl, - qortAddress: address, - }; + + return responseMessage } else { throw new Error("Unable to send buy order message"); } @@ -2351,6 +2357,8 @@ async function listenForChatMessageForBuyOrder({ senderPublicKey ); + return parsedMessageObj + // chrome.tabs.query({}, function (tabs) { // tabs.forEach((tab) => { // chrome.tabs.sendMessage(tab.id, { @@ -2999,12 +3007,14 @@ export const checkNewMessages = async () => { }, 10000); // Close after 5 seconds } const savedtimestampAfter = await getTimestampGroupAnnouncement(); + const targetOrigin = window.location.origin; + window.postMessage( { action: "SET_GROUP_ANNOUNCEMENTS", payload: savedtimestampAfter, }, - "*" + targetOrigin ); } catch (error) { } finally { @@ -3163,12 +3173,14 @@ export const checkThreads = async (bringBack) => { } } const savedtimestampAfter = await getTimestampGroupAnnouncement(); + const targetOrigin = window.location.origin; + window.postMessage( { action: "SET_GROUP_ANNOUNCEMENTS", payload: savedtimestampAfter, }, - "*" + targetOrigin ); } catch (error) { } finally { diff --git a/src/components/Apps/AppViewer.tsx b/src/components/Apps/AppViewer.tsx index 45318cf..bc89875 100644 --- a/src/components/Apps/AppViewer.tsx +++ b/src/components/Apps/AppViewer.tsx @@ -59,10 +59,10 @@ export const AppViewer = React.forwardRef(({ app , hide, isDevMode}, iframeRef) // Calculate the previous index and path const previousPageIndex = history.currentIndex - 1; const previousPath = history.customQDNHistoryPaths[previousPageIndex]; - + const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*"; // Signal non-manual navigation iframeRef.current.contentWindow.postMessage( - { action: 'PERFORMING_NON_MANUAL', currentIndex: previousPageIndex }, '*' + { action: 'PERFORMING_NON_MANUAL', currentIndex: previousPageIndex },targetOrigin ); // Update the current index locally changeCurrentIndex(previousPageIndex); @@ -83,10 +83,10 @@ export const AppViewer = React.forwardRef(({ app , hide, isDevMode}, iframeRef) window.removeEventListener('message', handleNavigationSuccess); reject(new Error("Navigation timeout")); }, 200); - + const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*"; // Send the navigation command after setting up the listener and timeout iframeRef.current.contentWindow.postMessage( - { action: 'NAVIGATE_TO_PATH', path: previousPath, requestedHandler: 'UI' }, '*' + { action: 'NAVIGATE_TO_PATH', path: previousPath, requestedHandler: 'UI' }, targetOrigin ); }); @@ -123,9 +123,10 @@ export const AppViewer = React.forwardRef(({ app , hide, isDevMode}, iframeRef) if (iframeRef.current && iframeRef.current.contentWindow) { + const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*"; iframeRef.current.contentWindow.postMessage( { action: 'NAVIGATE_FORWARD'}, - '*' + targetOrigin ); } else { console.log('Iframe not accessible or does not have a content window.'); diff --git a/src/components/Apps/useQortalMessageListener.tsx b/src/components/Apps/useQortalMessageListener.tsx index 210ee91..54ff99d 100644 --- a/src/components/Apps/useQortalMessageListener.tsx +++ b/src/components/Apps/useQortalMessageListener.tsx @@ -183,7 +183,7 @@ const UIQortalRequests = [ 'GET_WALLET_BALANCE', 'GET_USER_WALLET_INFO', 'GET_CROSSCHAIN_SERVER_INFO', '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' + 'ADD_FOREIGN_SERVER', 'REMOVE_FOREIGN_SERVER', 'GET_DAY_SUMMARY', 'CREATE_TRADE_BUY_ORDER' ]; @@ -534,10 +534,11 @@ isDOMContentLoaded: false executeEvent("addTab", { data: event?.data?.payload }) + const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*"; iframeRef.current.contentWindow.postMessage( { action: 'SET_TAB_SUCCESS', requestedHandler: 'UI',payload: { name: event?.data?.payload?.name - } }, '*' + } }, targetOrigin ); } diff --git a/src/messaging/messagesToBackground.tsx b/src/messaging/messagesToBackground.tsx index 2213d38..f252aa5 100644 --- a/src/messaging/messagesToBackground.tsx +++ b/src/messaging/messagesToBackground.tsx @@ -28,9 +28,9 @@ export const sendMessageBackground = (action, data = {}, timeout = 60000, isExte return new Promise((resolve, reject) => { const requestId = generateRequestId(); // Unique ID for each request callbackMap.set(requestId, { resolve, reject }); // Store both resolve and reject callbacks - + const targetOrigin = window.location.origin // Send the message with `backgroundMessage` type - window.postMessage({ type: "backgroundMessage", action, requestId, payload: data, isExtension }, "*"); + window.postMessage({ type: "backgroundMessage", action, requestId, payload: data, isExtension }, targetOrigin); // Set up a timeout to automatically reject if no response is received const timeoutId = setTimeout(() => { diff --git a/src/qortalRequests.ts b/src/qortalRequests.ts index d6df7a1..0290da3 100644 --- a/src/qortalRequests.ts +++ b/src/qortalRequests.ts @@ -1,4 +1,4 @@ -import { addForeignServer, addListItems, createPoll, decryptData, deleteListItems, deployAt, encryptData, getCrossChainServerInfo, getDaySummary, getForeignFee, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getWalletBalance, joinGroup, publishMultipleQDNResources, publishQDNResource, removeForeignServer, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, updateForeignFee, voteOnPoll } from "./qortalRequests/get"; +import { addForeignServer, addListItems, createBuyOrder, createPoll, decryptData, deleteListItems, deployAt, encryptData, getCrossChainServerInfo, getDaySummary, getForeignFee, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getWalletBalance, joinGroup, publishMultipleQDNResources, publishQDNResource, removeForeignServer, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, updateForeignFee, voteOnPoll } from "./qortalRequests/get"; import { getData, storeData } from "./utils/chromeStorage"; @@ -584,6 +584,26 @@ function setLocalStorage(key, data) { } break; } + + case "CREATE_TRADE_BUY_ORDER": { + try { + const res = await createBuyOrder(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; + } default: break; diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index b53fa5a..d8ccd4a 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -12,7 +12,8 @@ import { joinGroup as joinGroupFunc, sendQortFee, sendCoin as sendCoinFunc, - isUsingLocal + isUsingLocal, + createBuyOrderTx } from "../background"; import { getNameInfo } from "../backgroundFunctions/encryption"; import { showSaveFilePicker } from "../components/Apps/useQortalMessageListener"; @@ -32,12 +33,17 @@ import { createTransaction } from "../transactions/transactions"; import { mimeToExtensionMap } from "../utils/memeTypes"; + const btcFeePerByte = 0.00000100 const ltcFeePerByte = 0.00000030 const dogeFeePerByte = 0.00001000 const dgbFeePerByte = 0.00000010 const rvnFeePerByte = 0.00001125 +function roundUpToDecimals(number, decimals = 8) { + const factor = Math.pow(10, decimals); // Create a factor based on the number of decimals + return Math.ceil(+number * factor) / factor; +} const _createPoll = async ({pollName, pollDescription, options}, isFromExtension) => { const fee = await getFee("CREATE_POLL"); @@ -206,11 +212,12 @@ function getFileFromContentScript(fileId) { const requestId = `getFile_${fileId}_${Date.now()}`; fileRequestResolvers.set(requestId, { resolve, reject }); // Store resolvers by requestId + const targetOrigin = window.location.origin; // Send the request message window.postMessage( { action: "getFileFromIndexedDB", fileId, requestId }, - "*" + targetOrigin ); // Timeout to handle no response scenario @@ -253,11 +260,12 @@ async function getUserPermission(payload, isFromExtension) { return new Promise((resolve) => { const requestId = `qortalRequest_${Date.now()}`; responseResolvers.set(requestId, resolve); // Store resolver by requestId + const targetOrigin = window.location.origin; // Send the request message window.postMessage( { action: "QORTAL_REQUEST_PERMISSION", payload, requestId, isFromExtension }, - "*" + targetOrigin ); // Optional timeout to handle no response scenario @@ -2390,3 +2398,63 @@ export const sendCoin = async (data, isFromExtension) => { } } }; + + +export const createBuyOrder = async (data, isFromExtension) => { + + const requiredFields = [ + "crosschainAtInfo", + "processType" + ]; + 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 crosschainAtInfo = data.crosschainAtInfo; + const atAddresses = data.crosschainAtInfo?.map((order)=> order.qortalAtAddress); + const processType = data.processType; + if(processType !== 'local' && processType !== 'gateway'){ + throw new Error('Process Type must be either local or gateway') + } + + try { + const resPermission = await getUserPermission({ + text1: "Do you give this application permission to perform a buy order?", + text2: `${atAddresses?.length}${" "} + ${`buy order${ + atAddresses?.length === 1 ? "" : "s" + }`}`, + text3: `${crosschainAtInfo?.reduce((latest, cur) => { + return latest + +cur?.qortAmount; + }, 0)} QORT FOR ${roundUpToDecimals( + crosschainAtInfo?.reduce((latest, cur) => { + return latest + +cur?.foreignAmount; + }, 0) + )} + ${` ${crosschainAtInfo?.[0]?.foreignBlockchain}`}`, + highlightedText: `Using ${processType}`, + fee: '' + }, isFromExtension); + const { accepted } = resPermission; + if (accepted) { + const resBuyOrder = await createBuyOrderTx( + { + crosschainAtInfo, + useLocal: processType === 'local' ? true : false + } + ); + return resBuyOrder; + } else { + throw new Error("User declined request"); + } + } catch (error) { + throw new Error(error?.message || "Failed to submit trade order."); + } +}; \ No newline at end of file diff --git a/src/utils/indexedDB.ts b/src/utils/indexedDB.ts index ffcf38c..0364570 100644 --- a/src/utils/indexedDB.ts +++ b/src/utils/indexedDB.ts @@ -1,3 +1,6 @@ +import { openIndexedDB } from "../components/Apps/useQortalMessageListener"; +import { fileToBase64 } from "./fileReading"; + export async function handleGetFileFromIndexedDB(event) { try { const { fileId, requestId } = event.data; @@ -21,10 +24,11 @@ export async function handleGetFileFromIndexedDB(event) { deleteRequest.onsuccess = function () { try { - + const targetOrigin = window.location.origin; + window.postMessage( { action: "getFileFromIndexedDBResponse", requestId, result: base64String }, - "*" + targetOrigin ); } catch (error) { console.log('error', error) @@ -41,21 +45,24 @@ export async function handleGetFileFromIndexedDB(event) { result: null, error: "Failed to convert file to Base64", }); + const targetOrigin = window.location.origin; + window.postMessage( { action: "getFileFromIndexedDBResponse", requestId, result: null, error: "Failed to convert file to Base64" }, - "*" + targetOrigin ); } } else { console.error(`File with ID ${fileId} not found in IndexedDB`); - + const targetOrigin = window.location.origin; + window.postMessage( { action: "getFileFromIndexedDBResponse", requestId, result: null, error: 'File not found in IndexedDB' }, - "*" + targetOrigin ); } };