From 62aed9fea23db87999a781f81ad11cd150444f9f Mon Sep 17 00:00:00 2001 From: Phillip Lang Martinez Date: Fri, 12 Jul 2024 23:44:21 -0400 Subject: [PATCH 1/6] buyorder approval --- public/content-script.js | 33 ++- public/manifest.json | 8 +- public/memory-pow.wasm.full | Bin 0 -> 3399 bytes src/App.tsx | 143 +++++++++++- src/background.ts | 339 +++++++++++++++++++++++++++- src/transactions/ChatBase.ts | 145 ++++++++++++ src/transactions/ChatTransaction.ts | 92 ++++++++ src/transactions/signChat.ts | 40 ++++ src/transactions/transactions.ts | 3 +- 9 files changed, 781 insertions(+), 22 deletions(-) create mode 100644 public/memory-pow.wasm.full create mode 100644 src/transactions/ChatBase.ts create mode 100644 src/transactions/ChatTransaction.ts create mode 100644 src/transactions/signChat.ts diff --git a/public/content-script.js b/public/content-script.js index ff844c6..14419f3 100644 --- a/public/content-script.js +++ b/public/content-script.js @@ -10,7 +10,6 @@ // In your content script document.addEventListener('qortalExtensionRequests', async (event) => { const { type, payload, requestId, timeout } = event.detail; // Capture the requestId - if (type === 'REQUEST_USER_INFO') { const hostname = window.location.hostname const res = await connection(hostname) @@ -92,7 +91,37 @@ document.addEventListener('qortalExtensionRequests', async (event) => { })); } }); - } else if (type === 'REQUEST_AUTHENTICATION') { + } else if (type === 'REQUEST_BUY_ORDER') { + const hostname = window.location.hostname + const res = await connection(hostname) + if(!res){ + document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { + detail: { type: "BUY_ORDER", data: { + error: "Not authorized" + }, requestId } + })); + return + } + + chrome.runtime.sendMessage({ action: "buyOrder", payload: { + qortalAtAddress: payload.qortalAtAddress, + hostname + + }, timeout}, (response) => { + if (response.error) { + document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { + detail: { type: "BUY_ORDER", data: { + error: response.error + }, requestId } + })); + } else { + // Include the requestId in the detail when dispatching the response + document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { + detail: { type: "BUY_ORDER", data: response, requestId } + })); + } + }); + } else if (type === 'REQUEST_AUTHENTICATION') { const hostname = window.location.hostname const res = await connection(hostname) if(!res){ diff --git a/public/manifest.json b/public/manifest.json index 21ec40c..1ad34af 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Qortal", - "version": "1.0.0", + "version": "1.1.0", "icons": { "16": "qort.png", "32": "qort.png", @@ -16,10 +16,14 @@ }, "permissions": [ "storage", "system.display", "activeTab", "tabs" ], + "content_scripts": [ { "matches": [""], "js": ["content-script.js"] } - ] + ], + "content_security_policy": { + "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; connect-src 'self' https://api.qortal.org https://api2.qortal.org https://appnode.qortal.org https://apinode.qortalnodes.live https://apinode1.qortalnodes.live https://apinode2.qortalnodes.live https://apinode3.qortalnodes.live https://apinode4.qortalnodes.live;" + } } diff --git a/public/memory-pow.wasm.full b/public/memory-pow.wasm.full new file mode 100644 index 0000000000000000000000000000000000000000..073b179cab68d0eabc80c9b6ba9590d7e59c2e4f GIT binary patch literal 3399 zcma)8O^h2w7Jk)LZMWV2X^%(AL?e{yPWTICoC!lh_)Qx{$?z8zmR*v=cG`B&j3;*Y z(6%Ru6pazWh(8HMk#>cIGzfum1o5-d0tYS}cW)~M7ovsT6A@exD*+1cb&nl;5(8!R zd-dM;{;FQPy1;EU833?p^#s5P)@9ujqKgALTO-nCYf7GR&tFY6y=D-eQ9$7d6$Il- z;+ABW*-t+&^e)&>Ygf@20Mau(`46TS9Oo2fbgFKn;Z)l}*aFZ`2d$-g80@`$-wLoT zey3US!lMfhd2X%lFCO>8pmp0`P_7B=n***@gXT)7?d=7TahzH!aF$%Z*6=t>qv*C> z$Mb7k$vVzrBdE9yr{cCe&UMFG^4yhJ5{SmDi)9rfwcSd?i^W9x2cdFc`lC!;lOW+{ z4i+P3^ej$Xvy|R`t|6w7IAw{RrIvTUu%Az%j!UIncn%E=6qrBeA?$S!9JC zX4mCJ&rC^X3aMBi4x*EgDVaV?Wu#=bXJxQb>;$@Ck;`bO$#l=kVW%)yO6JI2lNkyI zc6ZqVT!$W&ZmN<|H5q)-qoqgjii3C*$jqU0k(^nS_5$|u^5x`HQdyy`Lw5?Dk)!zp zB@(n@8G}oHgk}kB$4=Zc9o|v(nVMJWEIM_6A$VN@5I{pPq8Zpi49AX`!4c#Nwgmoy z599m~$dV%V!Kt{!VEtb>QNKWv_>RbbkwB6NjmY9HoUo}7vuUKe7>PF^P2Gy*KO{~Q zy2a0;2!a-<(B9K`x(gzus}>0M~fcD+o+Y>%cCJ56N6fJalRHm{t6|B>e{c)UT1 zwq~BdxoFA%M=`2yUxYq`CdUcAgVLwoN6{hiKqP*SqEF)8k+0wY(Q@t~)W9bisdLB< zk=d=3|BC)?)E_2&Dcas4Vh?K-F!?RcwPfxbO3G_U?xBbZFCo+L`6`j#cx4+w zmHq*Lq7)mCfJ*@DqY^^V*lf$8uzkc-`BiB+%s){WPQuoQ(eF2*>R1#@7_C+tYf38{ zAIN|`>*uxMr(d21=cg_~4$;0Q2j`7TkVnm^eiBHNi1b&Z2Ie;qqu;1s0{!UMkDrd@ zI745z*(S5$Ga6}pj>(Peaecp4!RBn&st9*sWVQWDf3JRA!_#2&zdIwZQ)+Bybub1W zp0P$8_Q;WV$+4yiz6<@;&|5RHNg7u<`Smea%WnM1Lf2pP=s=8K_+R6FU&>8mGmo`b z>#u3|iqbE@tyDs9l@-|axuVK*#@6XvfjzMm4~H!wnZXJNq$AM*Ug z_R^37mdJlladpWJqvhR!6*r&~r5JH-d0q{OJdC01D~-Arf*<%*59)r+I}LDqY9$P6 loht6VqEQ<(null); const [decryptedWallet, setdecryptedWallet] = useState(null); const [requestConnection, setRequestConnection] = useState(null); + const [requestBuyOrder, setRequestBuyOrder] = useState(null); + const [requestAuthentication, setRequestAuthentication] = useState(null); const [userInfo, setUserInfo] = useState(null); const [balance, setBalance] = useState(null); @@ -266,6 +269,10 @@ function App() { // Update the component state with the received 'sendqort' state setRequestConnection(message.payload); setExtstate("web-app-request-connection"); + } else if (message.action === "UPDATE_STATE_REQUEST_BUY_ORDER") { + // Update the component state with the received 'sendqort' state + setRequestBuyOrder(message.payload); + setExtstate("web-app-request-buy-order"); } else if (message.action === "UPDATE_STATE_REQUEST_AUTHENTICATION") { // Update the component state with the received 'sendqort' state setRequestAuthentication(message.payload); @@ -301,10 +308,7 @@ function App() { ); return; } - // if (!paymentPassword) { - // setSendPaymentError("Please enter your wallet password"); - // return; - // } + setIsLoading(true) chrome.runtime.sendMessage( { @@ -321,7 +325,49 @@ function App() { if (response === true) { setExtstate("transfer-success-request"); setCountdown(null); - // setSendPaymentSuccess("Payment successfully sent"); + } else { + + setSendPaymentError( + response?.error || "Unable to perform payment. Please try again." + ); + } + setIsLoading(false) + } + ); + }; + + const confirmBuyOrder = (isDecline: boolean) => { + if (isDecline) { + chrome.runtime.sendMessage( + { + action: "buyOrderConfirmation", + payload: { + crosschainAtInfo: requestBuyOrder?.crosschainAtInfo, + interactionId: requestBuyOrder?.interactionId, + isDecline: true, + }, + }, + (response) => { + window.close(); + } + ); + return; + } + + setIsLoading(true) + chrome.runtime.sendMessage( + { + action: "buyOrderConfirmation", + payload: { + crosschainAtInfo: requestBuyOrder?.crosschainAtInfo, + interactionId: requestBuyOrder?.interactionId, + isDecline: false, + }, + }, + (response) => { + if (response === true) { + setExtstate("transfer-success-request"); + setCountdown(null); } else { setSendPaymentError( @@ -362,7 +408,7 @@ function App() { chrome.runtime.sendMessage({ action: "getWalletInfo" }, (response) => { if (response && response?.walletInfo) { setRawWallet(response?.walletInfo); - if(holdRefExtState.current === 'web-app-request-payment' || holdRefExtState.current === 'web-app-request-connection') return + if(holdRefExtState.current === 'web-app-request-payment' || holdRefExtState.current === 'web-app-request-connection' || holdRefExtState.current === 'web-app-request-buy-order') return setExtstate("authenticated"); } }); @@ -510,6 +556,7 @@ function App() { setRawWallet(null); setdecryptedWallet(null); setRequestConnection(null); + setRequestBuyOrder(null) setRequestAuthentication(null); setUserInfo(null); setBalance(null); @@ -816,6 +863,90 @@ function App() { Send + )} + {extState === "web-app-request-buy-order" && ( + <> + + + + The Application

{" "} + {requestBuyOrder?.hostname}

+ is requesting a buy order +
+ + + {requestBuyOrder?.crosschainAtInfo?.qortAmount} QORT + + + + FOR + + + + {requestBuyOrder?.crosschainAtInfo?.expectedForeignAmount} {requestBuyOrder?.crosschainAtInfo?.foreignBlockchain} + + {/* + + + Confirm Wallet Password + + + setPaymentPassword(e.target.value)} + /> */} + + + confirmBuyOrder(false)} + > + accept + + confirmBuyOrder(true)} + > + decline + + + {sendPaymentError} + )} {extState === "web-app-request-payment" && ( <> diff --git a/src/background.ts b/src/background.ts index 9181188..c268ea4 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,19 +1,13 @@ // @ts-nocheck import Base58 from "./deps/Base58"; +import { signChat } from "./transactions/signChat"; import { createTransaction } from "./transactions/transactions"; import { decryptChatMessage } from "./utils/decryptChatMessage"; import { decryptStoredWallet } from "./utils/decryptWallet"; import PhraseWallet from "./utils/generateWallet/phrase-wallet"; import { validateAddress } from "./utils/validateAddress"; +import { Sha256 } from 'asmcrypto.js' -// chrome.storage.local.clear(function() { -// var error = chrome.runtime.lastError; -// if (error) { -// console.error(error); -// } else { -// console.log('Local storage cleared'); -// } -// }); export const walletVersion = 2; // List of your API endpoints @@ -114,6 +108,15 @@ async function connection(hostname) { return isConnected; } +async function getTradeInfo(qortalAtAddress) { + const validApi = await findUsableApi(); + const response = await fetch(validApi + "/crosschain/trade/" + qortalAtAddress); + + if (!response?.ok) throw new Error("Cannot crosschain trade information"); + const data = await response.json(); + return data; +} + async function getBalanceInfo() { const wallet = await getSaveWallet(); const address = wallet.address0; @@ -125,6 +128,24 @@ async function getBalanceInfo() { return data; } +const processTransactionVersion2Chat = async (body: any, validApi: string) => { + // const validApi = await findUsableApi(); + const url = validApi + "/transactions/process?apiVersion=2"; + return fetch(url, { + method: "POST", + headers: {}, + body: Base58.encode(body), + }).then(async (response) => { + try { + const json = await response.clone().json(); + return json; + } catch (e) { + return await response.text(); + } + }); +}; + + const processTransactionVersion2 = async (body: any, validApi: string) => { // const validApi = await findUsableApi(); const url = validApi + "/transactions/process?apiVersion=2"; @@ -237,9 +258,11 @@ async function decryptWallet({password, wallet, walletVersion}) { const response = await decryptStoredWallet(password, wallet); const wallet2 = new PhraseWallet(response, walletVersion); const keyPair = wallet2._addresses[0].keyPair; + const ltcPrivateKey = wallet2._addresses[0].ltcWallet.derivedMasterPrivateKey const toSave = { privateKey: Base58.encode(keyPair.privateKey), - publicKey: Base58.encode(keyPair.publicKey) + publicKey: Base58.encode(keyPair.publicKey), + ltcPrivateKey: ltcPrivateKey } const dataString = JSON.stringify(toSave) await new Promise((resolve, reject) => { @@ -272,6 +295,152 @@ async function decryptWallet({password, wallet, walletVersion}) { } } +async function signChatFunc(chatBytesArray, chatNonce, validApi, keyPair ){ + let response + try { + const signedChatBytes = signChat( + chatBytesArray, + chatNonce, + keyPair + ) + const res = await processTransactionVersion2Chat(signedChatBytes, validApi) + response = res + } catch (e) { + console.error(e) + console.error(e.message) + response = false + } + return response +} +function sbrk(size, heap) { + let brk = 512 * 1024 // stack top + let old = brk + brk += size + if (brk > heap.length) throw new Error('heap exhausted') + return old +} + +const computePow = async ({chatBytes, path, difficulty}) => { + let response = null + await new Promise((resolve, reject) => { + const _chatBytesArray = Object.keys(chatBytes).map(function (key) { + return chatBytes[key] + }) + const chatBytesArray = new Uint8Array(_chatBytesArray) + const chatBytesHash = new Sha256().process(chatBytesArray).finish().result + const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 }) +const heap = new Uint8Array(memory.buffer) + + const hashPtr = sbrk(32, heap) + const hashAry = new Uint8Array(memory.buffer, hashPtr, 32) + hashAry.set(chatBytesHash) + const workBufferLength = 8 * 1024 * 1024 + const workBufferPtr = sbrk(workBufferLength, heap) + const importObject = { + env: { + memory: memory + } + } + function loadWebAssembly(filename, imports) { + // Fetch the file and compile it + return fetch(filename).then(response => response.arrayBuffer()).then(buffer => WebAssembly.compile(buffer)).then(module => { + // Create the instance. + return new WebAssembly.Instance(module, importObject) + }) + } + loadWebAssembly(path) + .then(wasmModule => { + response = { + nonce: wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty), chatBytesArray + } + resolve() + }) + }) + return response +} + +async function sendChat({qortAddress, recipientPublicKey, message }){ + + let _reference = new Uint8Array(64); + self.crypto.getRandomValues(_reference); + + let sendTimestamp = Date.now() + + let reference = Base58.encode(_reference) + const resKeyPair = await getKeyPair() + const parsedData = JSON.parse(resKeyPair) + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey + }; + const difficulty = 8; + const callRequest = `curl -X 'POST' 'http://localhost:12391/crosschain/tradebot/respond' -H 'accept: text/plain' -H 'X-API-KEY: keykeykeykey' -H 'Content-Type: application/json' -d '{ "atAddress": "${message.atAddress}", "foreignKey": "${message.foreignKey}", "receivingAddress": "${message.receivingAddress}" }'`; + +// Construct the final JSON object +const finalJson = { + callRequest: callRequest, + extra: "whatever additional data goes here" +}; + const messageStringified = JSON.stringify(finalJson) + const {chatBytes} = await createTransaction( + 18, + keyPair, + { + timestamp: sendTimestamp, + recipient: qortAddress, + recipientPublicKey: recipientPublicKey, + hasChatReference: 0, + message: messageStringified, + lastReference: reference, + proofOfWorkNonce: 0, + isEncrypted: 1, + isText: 1 + }, + + ) + const path = chrome.runtime.getURL('memory-pow.wasm.full'); + + + + + const {nonce, chatBytesArray} = await computePow({ chatBytes, path, difficulty }) + let _response = await signChatFunc(chatBytesArray, + nonce, "https://appnode.qortal.org", keyPair + ) + return _response +} + +async function createBuyOrderTx({crosschainAtInfo}){ + try { + const wallet = await getSaveWallet(); + const address = wallet.address0; + + const resKeyPair = await getKeyPair() + const parsedData = JSON.parse(resKeyPair) + const message = { + atAddress: crosschainAtInfo.qortalAtAddress, + foreignKey: parsedData.ltcPrivateKey, + receivingAddress: address + } + const res = await sendChat({qortAddress: "QXPejUe5Za1KD3zCMViWCX35AreMQ9H7ku", recipientPublicKey: "5hP6stDWybojoDw5t8z9D51nV945oMPX7qBd29rhX1G7", message }) + if(res?.signature){ + const decryptedMessage = await listenForChatMessageForBuyOrder({ + nodeBaseUrl: "https://appnode.qortal.org", + senderAddress: "QXPejUe5Za1KD3zCMViWCX35AreMQ9H7ku", + senderPublicKey: "5hP6stDWybojoDw5t8z9D51nV945oMPX7qBd29rhX1G7", + signature: res?.signature, + + }) + return decryptedMessage + } + + } catch (error) { + throw new Error(error.message); + } +} + async function sendCoin({ password, amount, receiver }, skipConfirmPassword) { try { const confirmReceiver = await getNameOrAddress(receiver); @@ -316,7 +485,7 @@ async function sendCoin({ password, amount, receiver }, skipConfirmPassword) { function fetchMessages(apiCall) { let retryDelay = 2000; // Start with a 2-second delay - const maxDuration = 360000; // Maximum duration set to 6 minutes + const maxDuration = 360000 * 2; // Maximum duration set to 12 minutes const startTime = Date.now(); // Record the start time // Promise to handle polling logic @@ -379,6 +548,42 @@ async function listenForChatMessage({ nodeBaseUrl, senderAddress, senderPublicKe } } +async function listenForChatMessageForBuyOrder({ nodeBaseUrl, senderAddress, senderPublicKey, signature }) { + try { + let validApi = ""; + const checkIfNodeBaseUrlIsAcceptable = apiEndpoints.find( + (item) => item === nodeBaseUrl + ); + if (checkIfNodeBaseUrlIsAcceptable) { + validApi = checkIfNodeBaseUrlIsAcceptable; + } else { + validApi = await findUsableApi(); + } + const wallet = await getSaveWallet(); + const address = wallet.address0; + const before = Date.now() + 1200000 + const after = Date.now() + const apiCall = `${validApi}/chat/messages?involving=${senderAddress}&involving=${address}&reverse=true&limit=1&before=${before}&after=${after}`; + const encodedMessageObj = await fetchMessages(apiCall) + + const resKeyPair = await getKeyPair() + const parsedData = JSON.parse(resKeyPair) + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey + }; + + const decodedMessage = decryptChatMessage(encodedMessageObj.data, keyPair.privateKey, senderPublicKey, encodedMessageObj.reference) + const parsedMessage = JSON.parse(decodedMessage) + return parsedMessage + } catch (error) { + console.error(error) + throw new Error(error.message); + } +} + chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request) { switch (request.action) { @@ -600,7 +805,87 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { }); } break; - case "connection": + case "buyOrder": { + const { qortalAtAddress, hostname } = request.payload; + getTradeInfo(qortalAtAddress) + .then((crosschainAtInfo) => { + const popupUrl = chrome.runtime.getURL("index.html"); + + chrome.windows.getAll( + { populate: true, windowTypes: ["popup"] }, + (windows) => { + // Attempt to find an existing popup window that has a tab with the correct URL + const existingPopup = windows.find( + (w) => + w.tabs && + w.tabs.some( + (tab) => tab.url && tab.url.startsWith(popupUrl) + ) + ); + if (existingPopup) { + // If the popup exists but is minimized or not focused, focus it + chrome.windows.update(existingPopup.id, { + focused: true, + state: "normal", + }); + } else { + // No existing popup found, create a new one + chrome.system.display.getInfo((displays) => { + // Assuming the primary display is the first one (adjust logic as needed) + const primaryDisplay = displays[0]; + const screenWidth = primaryDisplay.bounds.width; + const windowHeight = 500; // Your window height + const windowWidth = 400; // Your window width + + // Calculate left position for the window to appear on the right of the screen + const leftPosition = screenWidth - windowWidth; + + // Calculate top position for the window, adjust as desired + const topPosition = + (primaryDisplay.bounds.height - windowHeight) / 2; + + chrome.windows.create({ + url: chrome.runtime.getURL("index.html"), + type: "popup", + width: windowWidth, + height: windowHeight, + left: leftPosition, + top: 0, + }); + }); + } + + const interactionId = Date.now().toString(); // Simple example; consider a better unique ID + + setTimeout(() => { + chrome.runtime.sendMessage({ + action: "SET_COUNTDOWN", + payload: request.timeout ? 0.9 * request.timeout : 20, + }); + chrome.runtime.sendMessage({ + action: "UPDATE_STATE_REQUEST_BUY_ORDER", + payload: { + hostname, + crosschainAtInfo, + interactionId, + }, + }); + }, 500); + + // Store sendResponse callback with the interaction ID + pendingResponses.set(interactionId, sendResponse); + } + ); + + + }) + .catch((error) => { + console.error(error.message); + }); + } + + break; + case "connection": { const { hostname } = request.payload; connection(hostname) .then((isConnected) => { @@ -678,6 +963,8 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { .catch((error) => { console.error(error.message); }); + } + break; case "sendQort": { @@ -809,6 +1096,36 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { } + break; + case "buyOrderConfirmation": { + const { crosschainAtInfo, isDecline } = request.payload; + const interactionId2 = request.payload.interactionId; + // Retrieve the stored sendResponse callback + const originalSendResponse = pendingResponses.get(interactionId2); + + if (originalSendResponse) { + if (isDecline) { + originalSendResponse({ error: "User has declined" }); + sendResponse(false); + pendingResponses.delete(interactionId2); + return; + } + createBuyOrderTx({ crosschainAtInfo }) + .then((res) => { + sendResponse(true); + originalSendResponse(res); + pendingResponses.delete(interactionId2); + }) + .catch((error) => { + console.error(error.message); + sendResponse({ error: error.message }); + originalSendResponse({ error: error.message }); + }); + + } + } + + break; case "logout": { diff --git a/src/transactions/ChatBase.ts b/src/transactions/ChatBase.ts new file mode 100644 index 0000000..85cbdd9 --- /dev/null +++ b/src/transactions/ChatBase.ts @@ -0,0 +1,145 @@ +// @ts-nocheck + +import { QORT_DECIMALS, TX_TYPES } from '../constants/constants' +import nacl from '../deps/nacl-fast' +import Base58 from '../deps/Base58' +import utils from '../utils/utils' + +export default class ChatBase { + static get utils() { + return utils + } + + static get nacl() { + return nacl + } + + static get Base58() { + return Base58 + } + + constructor() { + this.fee = 0 + this.groupID = 0 + this.tests = [ + () => { + if (!(this._type >= 1 && this._type in TX_TYPES)) { + return 'Invalid type: ' + this.type + } + return true + }, + () => { + if (this._fee < 0) { + return 'Invalid fee: ' + this._fee / QORT_DECIMALS + } + return true + }, + () => { + if (this._groupID < 0 || !Number.isInteger(this._groupID)) { + return 'Invalid groupID: ' + this._groupID + } + return true + }, + () => { + if (!(new Date(this._timestamp)).getTime() > 0) { + return 'Invalid timestamp: ' + this._timestamp + } + return true + }, + () => { + if (!(this._lastReference instanceof Uint8Array && this._lastReference.byteLength == 64)) { + return 'Invalid last reference: ' + this._lastReference + } + return true + }, + () => { + if (!(this._keyPair)) { + return 'keyPair must be specified' + } + if (!(this._keyPair.publicKey instanceof Uint8Array && this._keyPair.publicKey.byteLength === 32)) { + return 'Invalid publicKey' + } + if (!(this._keyPair.privateKey instanceof Uint8Array && this._keyPair.privateKey.byteLength === 64)) { + return 'Invalid privateKey' + } + return true + } + ] + } + + set keyPair(keyPair) { + this._keyPair = keyPair + } + + set type(type) { + this.typeText = TX_TYPES[type] + this._type = type + this._typeBytes = this.constructor.utils.int32ToBytes(this._type) + } + + set groupID(groupID) { + this._groupID = groupID + this._groupIDBytes = this.constructor.utils.int32ToBytes(this._groupID) + } + + set timestamp(timestamp) { + this._timestamp = timestamp + this._timestampBytes = this.constructor.utils.int64ToBytes(this._timestamp) + } + + set fee(fee) { + this._fee = fee * QORT_DECIMALS + this._feeBytes = this.constructor.utils.int64ToBytes(this._fee) + } + + set lastReference(lastReference) { + this._lastReference = lastReference instanceof Uint8Array ? lastReference : this.constructor.Base58.decode(lastReference) + } + + get params() { + return [ + this._typeBytes, + this._timestampBytes, + this._groupIDBytes, + this._lastReference, + this._keyPair.publicKey + ] + } + + get chatBytes() { + const isValid = this.validParams() + if (!isValid.valid) { + throw new Error(isValid.message) + } + + let result = new Uint8Array() + + this.params.forEach(item => { + result = this.constructor.utils.appendBuffer(result, item) + }) + + this._chatBytes = result + + return this._chatBytes + } + + validParams() { + let finalResult = { + valid: true + } + + this.tests.some(test => { + const result = test() + if (result !== true) { + finalResult = { + valid: false, + message: result + } + return true + } + }) + + return finalResult + } + +} diff --git a/src/transactions/ChatTransaction.ts b/src/transactions/ChatTransaction.ts new file mode 100644 index 0000000..ec30c8c --- /dev/null +++ b/src/transactions/ChatTransaction.ts @@ -0,0 +1,92 @@ +// @ts-nocheck + +import ChatBase from './ChatBase' +import nacl from '../deps/nacl-fast' +import ed2curve from '../deps/ed2curve' +import { Sha256 } from 'asmcrypto.js' +import { CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP } from '../constants/constants' + +export default class ChatTransaction extends ChatBase { + constructor() { + super() + this.type = 18 + this.fee = 0 + } + + set recipientPublicKey(recipientPublicKey) { + this._base58RecipientPublicKey = recipientPublicKey instanceof Uint8Array ? this.constructor.Base58.encode(recipientPublicKey) : recipientPublicKey + this._recipientPublicKey = this.constructor.Base58.decode(this._base58RecipientPublicKey) + } + + set proofOfWorkNonce(proofOfWorkNonce) { + this._proofOfWorkNonce = this.constructor.utils.int32ToBytes(proofOfWorkNonce) + } + + set recipient(recipient) { + this._recipient = recipient instanceof Uint8Array ? recipient : this.constructor.Base58.decode(recipient) + this._hasReceipient = new Uint8Array(1) + this._hasReceipient[0] = 1 + } + + set hasChatReference(hasChatReference) { + this._hasChatReference = new Uint8Array(1) + this._hasChatReference[0] = hasChatReference + } + + set chatReference(chatReference) { + this._chatReference = chatReference instanceof Uint8Array ? chatReference : this.constructor.Base58.decode(chatReference) + } + + set message(message) { + this.messageText = message; + this._message = this.constructor.utils.stringtoUTF8Array(message) + this._messageLength = this.constructor.utils.int32ToBytes(this._message.length) + } + + set isEncrypted(isEncrypted) { + this._isEncrypted = new Uint8Array(1) + this._isEncrypted[0] = isEncrypted + + if (isEncrypted === 1) { + const convertedPrivateKey = ed2curve.convertSecretKey(this._keyPair.privateKey) + const convertedPublicKey = ed2curve.convertPublicKey(this._recipientPublicKey) + const sharedSecret = new Uint8Array(32) + nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedPublicKey) + + this._chatEncryptionSeed = new Sha256().process(sharedSecret).finish().result + this._encryptedMessage = nacl.secretbox(this._message, this._lastReference.slice(0, 24), this._chatEncryptionSeed) + } + + this._myMessage = isEncrypted === 1 ? this._encryptedMessage : this._message + this._myMessageLenth = isEncrypted === 1 ? this.constructor.utils.int32ToBytes(this._myMessage.length) : this._messageLength + } + + set isText(isText) { + this._isText = new Uint8Array(1) + this._isText[0] = isText + } + + get params() { + const params = super.params + params.push( + this._proofOfWorkNonce, + this._hasReceipient, + this._recipient, + this._myMessageLenth, + this._myMessage, + this._isEncrypted, + this._isText, + this._feeBytes + ) + + // After the feature trigger timestamp we need to include chat reference + if (new Date(this._timestamp).getTime() >= CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP) { + params.push(this._hasChatReference) + + if (this._hasChatReference[0] == 1) { + params.push(this._chatReference) + } + } + return params + } +} diff --git a/src/transactions/signChat.ts b/src/transactions/signChat.ts new file mode 100644 index 0000000..20b2240 --- /dev/null +++ b/src/transactions/signChat.ts @@ -0,0 +1,40 @@ +// @ts-nocheck + +import nacl from '../deps/nacl-fast' +import utils from '../utils/utils' + +export const signChat = (chatBytes, nonce, keyPair) => { + + if (!chatBytes) { + throw new Error('Chat Bytes not defined') + } + + if (!nonce) { + throw new Error('Nonce not defined') + } + + if (!keyPair) { + throw new Error('keyPair not defined') + } + + const _nonce = utils.int32ToBytes(nonce) + + if (chatBytes.length === undefined) { + const _chatBytesBuffer = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; }) + + const chatBytesBuffer = new Uint8Array(_chatBytesBuffer) + chatBytesBuffer.set(_nonce, 112) + + const signature = nacl.sign.detached(chatBytesBuffer, keyPair.privateKey) + + return utils.appendBuffer(chatBytesBuffer, signature) + } else { + const chatBytesBuffer = new Uint8Array(chatBytes) + chatBytesBuffer.set(_nonce, 112) + + const signature = nacl.sign.detached(chatBytesBuffer, keyPair.privateKey) + + return utils.appendBuffer(chatBytesBuffer, signature) + } +} + diff --git a/src/transactions/transactions.ts b/src/transactions/transactions.ts index b809830..b2af068 100644 --- a/src/transactions/transactions.ts +++ b/src/transactions/transactions.ts @@ -1,11 +1,12 @@ // @ts-nocheck import PaymentTransaction from './PaymentTransaction.js' +import ChatTransaction from './ChatTransaction.js' export const transactionTypes = { 2: PaymentTransaction, - + 18: ChatTransaction } From f6c0afbc40e20626c809c2fdf7a1368361be254b Mon Sep 17 00:00:00 2001 From: Phillip Lang Martinez Date: Mon, 15 Jul 2024 22:07:57 -0400 Subject: [PATCH 2/6] draft completion --- public/content-script.js | 12 +- src/background.ts | 667 +++++++++++++++++++---------------- src/transactions/signChat.ts | 1 - 3 files changed, 376 insertions(+), 304 deletions(-) diff --git a/public/content-script.js b/public/content-script.js index 14419f3..d3d2bd5 100644 --- a/public/content-script.js +++ b/public/content-script.js @@ -216,5 +216,13 @@ chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { type: "LOGOUT", from: 'qortal' }, "*"); - } -}); \ No newline at end of file + } else if (message.type === "RESPONSE_FOR_TRADES") { + // Notify the web page + window.postMessage({ + type: "RESPONSE_FOR_TRADES", + from: 'qortal', + payload: message.message + }, "*"); +} +}); + diff --git a/src/background.ts b/src/background.ts index c268ea4..d20662e 100644 --- a/src/background.ts +++ b/src/background.ts @@ -22,6 +22,9 @@ const apiEndpoints = [ "https://apinode4.qortalnodes.live", ]; +const buyTradeNodeBaseUrl = 'https://appnode.qortal.org' +const proxyAccountAddress = 'QXPejUe5Za1KD3zCMViWCX35AreMQ9H7ku' +const proxyAccountPublicKey = '5hP6stDWybojoDw5t8z9D51nV945oMPX7qBd29rhX1G7' const pendingResponses = new Map(); // Function to check each API endpoint @@ -109,9 +112,8 @@ async function connection(hostname) { } async function getTradeInfo(qortalAtAddress) { - const validApi = await findUsableApi(); - const response = await fetch(validApi + "/crosschain/trade/" + qortalAtAddress); - + const response = await fetch(buyTradeNodeBaseUrl + "/crosschain/trade/" + qortalAtAddress); + console.log({}) if (!response?.ok) throw new Error("Cannot crosschain trade information"); const data = await response.json(); return data; @@ -253,7 +255,7 @@ async function getNameOrAddress(receiver) { } } -async function decryptWallet({password, wallet, walletVersion}) { +async function decryptWallet({ password, wallet, walletVersion }) { try { const response = await decryptStoredWallet(password, wallet); const wallet2 = new PhraseWallet(response, walletVersion); @@ -261,8 +263,8 @@ async function decryptWallet({password, wallet, walletVersion}) { const ltcPrivateKey = wallet2._addresses[0].ltcWallet.derivedMasterPrivateKey const toSave = { privateKey: Base58.encode(keyPair.privateKey), - publicKey: Base58.encode(keyPair.publicKey), - ltcPrivateKey: ltcPrivateKey + publicKey: Base58.encode(keyPair.publicKey), + ltcPrivateKey: ltcPrivateKey } const dataString = JSON.stringify(toSave) await new Promise((resolve, reject) => { @@ -288,154 +290,161 @@ async function decryptWallet({password, wallet, walletVersion}) { }); }); - return true; + return true; } catch (error) { - console.log({error}) + console.log({ error }) throw new Error(error.message); } } -async function signChatFunc(chatBytesArray, chatNonce, validApi, keyPair ){ +async function signChatFunc(chatBytesArray, chatNonce, validApi, keyPair) { + console.log({ chatBytesArray, chatNonce, validApi, keyPair }) let response - try { - const signedChatBytes = signChat( - chatBytesArray, - chatNonce, - keyPair - ) - const res = await processTransactionVersion2Chat(signedChatBytes, validApi) - response = res - } catch (e) { - console.error(e) - console.error(e.message) - response = false - } - return response + try { + const signedChatBytes = signChat( + chatBytesArray, + chatNonce, + keyPair + ) + const res = await processTransactionVersion2Chat(signedChatBytes, validApi) + response = res + } catch (e) { + console.error(e) + console.error(e.message) + response = false + } + return response } function sbrk(size, heap) { - let brk = 512 * 1024 // stack top - let old = brk - brk += size - if (brk > heap.length) throw new Error('heap exhausted') - return old + let brk = 512 * 1024 // stack top + let old = brk + brk += size + if (brk > heap.length) throw new Error('heap exhausted') + return old } -const computePow = async ({chatBytes, path, difficulty}) => { - let response = null - await new Promise((resolve, reject) => { - const _chatBytesArray = Object.keys(chatBytes).map(function (key) { - return chatBytes[key] - }) - const chatBytesArray = new Uint8Array(_chatBytesArray) - const chatBytesHash = new Sha256().process(chatBytesArray).finish().result +const computePow = async ({ chatBytes, path, difficulty }) => { + let response = null + await new Promise((resolve, reject) => { + const _chatBytesArray = Object.keys(chatBytes).map(function (key) { + return chatBytes[key] + }) + const chatBytesArray = new Uint8Array(_chatBytesArray) + const chatBytesHash = new Sha256().process(chatBytesArray).finish().result const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 }) -const heap = new Uint8Array(memory.buffer) + const heap = new Uint8Array(memory.buffer) - const hashPtr = sbrk(32, heap) - const hashAry = new Uint8Array(memory.buffer, hashPtr, 32) - hashAry.set(chatBytesHash) - const workBufferLength = 8 * 1024 * 1024 - const workBufferPtr = sbrk(workBufferLength, heap) - const importObject = { - env: { - memory: memory - } - } - function loadWebAssembly(filename, imports) { - // Fetch the file and compile it - return fetch(filename).then(response => response.arrayBuffer()).then(buffer => WebAssembly.compile(buffer)).then(module => { - // Create the instance. - return new WebAssembly.Instance(module, importObject) - }) - } - loadWebAssembly(path) - .then(wasmModule => { - response = { - nonce: wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty), chatBytesArray - } - resolve() - }) - }) - return response + const hashPtr = sbrk(32, heap) + const hashAry = new Uint8Array(memory.buffer, hashPtr, 32) + hashAry.set(chatBytesHash) + const workBufferLength = 8 * 1024 * 1024 + const workBufferPtr = sbrk(workBufferLength, heap) + const importObject = { + env: { + memory: memory + } + } + function loadWebAssembly(filename, imports) { + // Fetch the file and compile it + return fetch(filename).then(response => response.arrayBuffer()).then(buffer => WebAssembly.compile(buffer)).then(module => { + // Create the instance. + return new WebAssembly.Instance(module, importObject) + }) + } + loadWebAssembly(path) + .then(wasmModule => { + response = { + nonce: wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty), chatBytesArray + } + resolve() + }) + }) + return response } -async function sendChat({qortAddress, recipientPublicKey, message }){ - +async function sendChat({ qortAddress, recipientPublicKey, message }) { + let _reference = new Uint8Array(64); self.crypto.getRandomValues(_reference); - let sendTimestamp = Date.now() + let sendTimestamp = Date.now() - let reference = Base58.encode(_reference) - const resKeyPair = await getKeyPair() - const parsedData = JSON.parse(resKeyPair) - const uint8PrivateKey = Base58.decode(parsedData.privateKey); - const uint8PublicKey = Base58.decode(parsedData.publicKey); - const keyPair = { - privateKey: uint8PrivateKey, - publicKey: uint8PublicKey - }; - const difficulty = 8; - const callRequest = `curl -X 'POST' 'http://localhost:12391/crosschain/tradebot/respond' -H 'accept: text/plain' -H 'X-API-KEY: keykeykeykey' -H 'Content-Type: application/json' -d '{ "atAddress": "${message.atAddress}", "foreignKey": "${message.foreignKey}", "receivingAddress": "${message.receivingAddress}" }'`; + let reference = Base58.encode(_reference) + const resKeyPair = await getKeyPair() + const parsedData = JSON.parse(resKeyPair) + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey + }; + const difficulty = 8; + const jsonData = { + atAddress: message.atAddress, + foreignKey: message.foreignKey, + receivingAddress: message.receivingAddress + }; + const finalJson = { + callRequest: jsonData, + extra: "whatever additional data goes here" + }; + const messageStringified = JSON.stringify(finalJson) + const { chatBytes } = await createTransaction( + 18, + keyPair, + { + timestamp: sendTimestamp, + recipient: qortAddress, + recipientPublicKey: recipientPublicKey, + hasChatReference: 0, + message: messageStringified, + lastReference: reference, + proofOfWorkNonce: 0, + isEncrypted: 1, + isText: 1 + }, -// Construct the final JSON object -const finalJson = { - callRequest: callRequest, - extra: "whatever additional data goes here" -}; - const messageStringified = JSON.stringify(finalJson) - const {chatBytes} = await createTransaction( - 18, - keyPair, - { - timestamp: sendTimestamp, - recipient: qortAddress, - recipientPublicKey: recipientPublicKey, - hasChatReference: 0, - message: messageStringified, - lastReference: reference, - proofOfWorkNonce: 0, - isEncrypted: 1, - isText: 1 - }, - - ) - const path = chrome.runtime.getURL('memory-pow.wasm.full'); - + ) + const path = chrome.runtime.getURL('memory-pow.wasm.full'); - - const {nonce, chatBytesArray} = await computePow({ chatBytes, path, difficulty }) - let _response = await signChatFunc(chatBytesArray, - nonce, "https://appnode.qortal.org", keyPair - ) - return _response + + + const { nonce, chatBytesArray } = await computePow({ chatBytes, path, difficulty }) + let _response = await signChatFunc(chatBytesArray, + nonce, "https://appnode.qortal.org", keyPair + ) + if (_response?.error) { + throw new Error(_response?.message) + } + return _response } -async function createBuyOrderTx({crosschainAtInfo}){ +async function createBuyOrderTx({ crosschainAtInfo }) { try { const wallet = await getSaveWallet(); const address = wallet.address0; const resKeyPair = await getKeyPair() - const parsedData = JSON.parse(resKeyPair) + const parsedData = JSON.parse(resKeyPair) const message = { atAddress: crosschainAtInfo.qortalAtAddress, foreignKey: parsedData.ltcPrivateKey, receivingAddress: address } - const res = await sendChat({qortAddress: "QXPejUe5Za1KD3zCMViWCX35AreMQ9H7ku", recipientPublicKey: "5hP6stDWybojoDw5t8z9D51nV945oMPX7qBd29rhX1G7", message }) - if(res?.signature){ - const decryptedMessage = await listenForChatMessageForBuyOrder({ - nodeBaseUrl: "https://appnode.qortal.org", - senderAddress: "QXPejUe5Za1KD3zCMViWCX35AreMQ9H7ku", - senderPublicKey: "5hP6stDWybojoDw5t8z9D51nV945oMPX7qBd29rhX1G7", + const res = await sendChat({ qortAddress: proxyAccountAddress, recipientPublicKey: proxyAccountPublicKey, message }) + if (res?.signature) { + + listenForChatMessageForBuyOrder({ + nodeBaseUrl: buyTradeNodeBaseUrl, + senderAddress: proxyAccountAddress, + senderPublicKey: proxyAccountPublicKey, signature: res?.signature, - + }) - return decryptedMessage + return { atAddress: crosschainAtInfo.qortalAtAddress, chatSignature: res?.signature, node: buyTradeNodeBaseUrl } } - + } catch (error) { throw new Error(error.message); } @@ -448,7 +457,7 @@ async function sendCoin({ password, amount, receiver }, skipConfirmPassword) { throw new Error("Invalid receiver address or name"); const wallet = await getSaveWallet(); let keyPair = '' - if(skipConfirmPassword){ + if (skipConfirmPassword) { const resKeyPair = await getKeyPair() const parsedData = JSON.parse(resKeyPair) const uint8PrivateKey = Base58.decode(parsedData.privateKey); @@ -459,11 +468,11 @@ async function sendCoin({ password, amount, receiver }, skipConfirmPassword) { }; } else { const response = await decryptStoredWallet(password, wallet); - const wallet2 = new PhraseWallet(response, walletVersion); + const wallet2 = new PhraseWallet(response, walletVersion); - keyPair = wallet2._addresses[0].keyPair + keyPair = wallet2._addresses[0].keyPair } - + const lastRef = await getLastRef(); const fee = await sendQortFee(); @@ -490,26 +499,78 @@ function fetchMessages(apiCall) { // Promise to handle polling logic return new Promise((resolve, reject) => { - const attemptFetch = async () => { - if (Date.now() - startTime > maxDuration) { - return reject(new Error("Maximum polling time exceeded")); - } + const attemptFetch = async () => { + if (Date.now() - startTime > maxDuration) { + return reject(new Error("Maximum polling time exceeded")); + } - try { - const response = await fetch(apiCall); - const data = await response.json(); - if (data && data.length > 0) { - resolve(data[0]); // Resolve the promise when data is found - } else { - setTimeout(attemptFetch, retryDelay); - retryDelay = Math.min(retryDelay * 2, 360000); // Ensure delay does not exceed 6 minutes - } - } catch (error) { - reject(error); // Reject the promise on error - } - }; + try { + const response = await fetch(apiCall); + const data = await response.json(); + if (data && data.length > 0) { + resolve(data[0]); // Resolve the promise when data is found + } else { + setTimeout(attemptFetch, retryDelay); + retryDelay = Math.min(retryDelay * 2, 360000); // Ensure delay does not exceed 6 minutes + } + } catch (error) { + reject(error); // Reject the promise on error + } + }; - attemptFetch(); // Initial call to start the polling + attemptFetch(); // Initial call to start the polling + }); +} + +function fetchMessagesForBuyOrders(apiCall, signature, senderPublicKey) { + let retryDelay = 2000; // Start with a 2-second delay + const maxDuration = 360000 * 2; // Maximum duration set to 12 minutes + const startTime = Date.now(); // Record the start time + let triedChatMessage = [] + // Promise to handle polling logic + return new Promise((resolve, reject) => { + const attemptFetch = async () => { + if (Date.now() - startTime > maxDuration) { + return reject(new Error("Maximum polling time exceeded")); + } + + try { + const response = await fetch(apiCall); + let data = await response.json(); + data = data.filter((item) => !triedChatMessage.includes(item.signature)) + console.log({data}) + if (data && data.length > 0) { + const encodedMessageObj = data[0] + const resKeyPair = await getKeyPair() + const parsedData = JSON.parse(resKeyPair) + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey + }; + + const decodedMessage = decryptChatMessage(encodedMessageObj.data, keyPair.privateKey, senderPublicKey, encodedMessageObj.reference) + const parsedMessage = JSON.parse(decodedMessage) + console.log({parsedMessage}) + if (parsedMessage?.extra?.chatRequestSignature === signature) { + resolve(parsedMessage); + } else { + triedChatMessage.push(encodedMessageObj.signature) + setTimeout(attemptFetch, retryDelay); + retryDelay = Math.min(retryDelay * 2, 360000); // Ensure delay does not exceed 6 minutes + } + // Resolve the promise when data is found + } else { + setTimeout(attemptFetch, retryDelay); + retryDelay = Math.min(retryDelay * 2, 360000); // Ensure delay does not exceed 6 minutes + } + } catch (error) { + reject(error); // Reject the promise on error + } + }; + + attemptFetch(); // Initial call to start the polling }); } @@ -530,17 +591,17 @@ async function listenForChatMessage({ nodeBaseUrl, senderAddress, senderPublicKe const after = timestamp - 5000 const apiCall = `${validApi}/chat/messages?involving=${senderAddress}&involving=${address}&reverse=true&limit=1&before=${before}&after=${after}`; const encodedMessageObj = await fetchMessages(apiCall) - + const resKeyPair = await getKeyPair() - const parsedData = JSON.parse(resKeyPair) - const uint8PrivateKey = Base58.decode(parsedData.privateKey); - const uint8PublicKey = Base58.decode(parsedData.publicKey); - const keyPair = { - privateKey: uint8PrivateKey, - publicKey: uint8PublicKey - }; - - const decodedMessage = decryptChatMessage(encodedMessageObj.data, keyPair.privateKey, senderPublicKey, encodedMessageObj.reference) + const parsedData = JSON.parse(resKeyPair) + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey + }; + + const decodedMessage = decryptChatMessage(encodedMessageObj.data, keyPair.privateKey, senderPublicKey, encodedMessageObj.reference) return { secretCode: decodedMessage }; } catch (error) { console.error(error) @@ -564,20 +625,24 @@ async function listenForChatMessageForBuyOrder({ nodeBaseUrl, senderAddress, sen const before = Date.now() + 1200000 const after = Date.now() const apiCall = `${validApi}/chat/messages?involving=${senderAddress}&involving=${address}&reverse=true&limit=1&before=${before}&after=${after}`; - const encodedMessageObj = await fetchMessages(apiCall) - - const resKeyPair = await getKeyPair() - const parsedData = JSON.parse(resKeyPair) - const uint8PrivateKey = Base58.decode(parsedData.privateKey); - const uint8PublicKey = Base58.decode(parsedData.publicKey); - const keyPair = { - privateKey: uint8PrivateKey, - publicKey: uint8PublicKey - }; - - const decodedMessage = decryptChatMessage(encodedMessageObj.data, keyPair.privateKey, senderPublicKey, encodedMessageObj.reference) - const parsedMessage = JSON.parse(decodedMessage) - return parsedMessage + const parsedMessageObj = await fetchMessagesForBuyOrders(apiCall, signature, senderPublicKey) + + // const resKeyPair = await getKeyPair() + // const parsedData = JSON.parse(resKeyPair) + // const uint8PrivateKey = Base58.decode(parsedData.privateKey); + // const uint8PublicKey = Base58.decode(parsedData.publicKey); + // const keyPair = { + // privateKey: uint8PrivateKey, + // publicKey: uint8PublicKey + // }; + + // const decodedMessage = decryptChatMessage(encodedMessageObj.data, keyPair.privateKey, senderPublicKey, encodedMessageObj.reference) + // const parsedMessage = JSON.parse(decodedMessage) + chrome.tabs.query({}, function (tabs) { + tabs.forEach(tab => { + chrome.tabs.sendMessage(tab.id, { type: "RESPONSE_FOR_TRADES", message: parsedMessageObj }); + }); + }); } catch (error) { console.error(error) throw new Error(error.message); @@ -601,21 +666,21 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { }); break; case "getWalletInfo": - - getKeyPair().then(()=> { - chrome.storage.local.get(["walletInfo"], (result) => { - if (chrome.runtime.lastError) { - sendResponse({ error: chrome.runtime.lastError.message }); - } else if (result.walletInfo) { - sendResponse({ walletInfo: result.walletInfo }); - } else { - sendResponse({ error: "No wallet info found" }); - } - }); - }).catch((error)=> { - sendResponse({ error: error.message }); - }) - + + getKeyPair().then(() => { + chrome.storage.local.get(["walletInfo"], (result) => { + if (chrome.runtime.lastError) { + sendResponse({ error: chrome.runtime.lastError.message }); + } else if (result.walletInfo) { + sendResponse({ walletInfo: result.walletInfo }); + } else { + sendResponse({ error: "No wallet info found" }); + } + }); + }).catch((error) => { + sendResponse({ error: error.message }); + }) + break; case "validApi": findUsableApi() @@ -644,21 +709,21 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { console.error(error.message); }); break; - case "decryptWallet": { - const { password, wallet } = request.payload; + case "decryptWallet": { + const { password, wallet } = request.payload; - decryptWallet({ - password, wallet, walletVersion + decryptWallet({ + password, wallet, walletVersion + }) + .then((hasDecrypted) => { + sendResponse(hasDecrypted); }) - .then((hasDecrypted) => { - sendResponse(hasDecrypted); - }) - .catch((error) => { - sendResponse({ error: error?.message }); - console.error(error.message); - }); - } - + .catch((error) => { + sendResponse({ error: error?.message }); + console.error(error.message); + }); + } + break; case "balance": getBalanceInfo() @@ -805,86 +870,86 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { }); } break; - case "buyOrder": { - const { qortalAtAddress, hostname } = request.payload; - getTradeInfo(qortalAtAddress) - .then((crosschainAtInfo) => { - const popupUrl = chrome.runtime.getURL("index.html"); - - chrome.windows.getAll( - { populate: true, windowTypes: ["popup"] }, - (windows) => { - // Attempt to find an existing popup window that has a tab with the correct URL - const existingPopup = windows.find( - (w) => - w.tabs && - w.tabs.some( - (tab) => tab.url && tab.url.startsWith(popupUrl) - ) - ); - if (existingPopup) { - // If the popup exists but is minimized or not focused, focus it - chrome.windows.update(existingPopup.id, { - focused: true, - state: "normal", - }); - } else { - // No existing popup found, create a new one - chrome.system.display.getInfo((displays) => { - // Assuming the primary display is the first one (adjust logic as needed) - const primaryDisplay = displays[0]; - const screenWidth = primaryDisplay.bounds.width; - const windowHeight = 500; // Your window height - const windowWidth = 400; // Your window width - - // Calculate left position for the window to appear on the right of the screen - const leftPosition = screenWidth - windowWidth; - - // Calculate top position for the window, adjust as desired - const topPosition = - (primaryDisplay.bounds.height - windowHeight) / 2; - - chrome.windows.create({ - url: chrome.runtime.getURL("index.html"), - type: "popup", - width: windowWidth, - height: windowHeight, - left: leftPosition, - top: 0, - }); - }); - } - - const interactionId = Date.now().toString(); // Simple example; consider a better unique ID - - setTimeout(() => { - chrome.runtime.sendMessage({ - action: "SET_COUNTDOWN", - payload: request.timeout ? 0.9 * request.timeout : 20, - }); - chrome.runtime.sendMessage({ - action: "UPDATE_STATE_REQUEST_BUY_ORDER", - payload: { - hostname, - crosschainAtInfo, - interactionId, - }, - }); - }, 500); - - // Store sendResponse callback with the interaction ID - pendingResponses.set(interactionId, sendResponse); - } - ); - + case "buyOrder": { + const { qortalAtAddress, hostname } = request.payload; + getTradeInfo(qortalAtAddress) + .then((crosschainAtInfo) => { + const popupUrl = chrome.runtime.getURL("index.html"); - }) - .catch((error) => { - console.error(error.message); - }); - } - - break; + chrome.windows.getAll( + { populate: true, windowTypes: ["popup"] }, + (windows) => { + // Attempt to find an existing popup window that has a tab with the correct URL + const existingPopup = windows.find( + (w) => + w.tabs && + w.tabs.some( + (tab) => tab.url && tab.url.startsWith(popupUrl) + ) + ); + if (existingPopup) { + // If the popup exists but is minimized or not focused, focus it + chrome.windows.update(existingPopup.id, { + focused: true, + state: "normal", + }); + } else { + // No existing popup found, create a new one + chrome.system.display.getInfo((displays) => { + // Assuming the primary display is the first one (adjust logic as needed) + const primaryDisplay = displays[0]; + const screenWidth = primaryDisplay.bounds.width; + const windowHeight = 500; // Your window height + const windowWidth = 400; // Your window width + + // Calculate left position for the window to appear on the right of the screen + const leftPosition = screenWidth - windowWidth; + + // Calculate top position for the window, adjust as desired + const topPosition = + (primaryDisplay.bounds.height - windowHeight) / 2; + + chrome.windows.create({ + url: chrome.runtime.getURL("index.html"), + type: "popup", + width: windowWidth, + height: windowHeight, + left: leftPosition, + top: 0, + }); + }); + } + + const interactionId = Date.now().toString(); // Simple example; consider a better unique ID + + setTimeout(() => { + chrome.runtime.sendMessage({ + action: "SET_COUNTDOWN", + payload: request.timeout ? 0.9 * request.timeout : 20, + }); + chrome.runtime.sendMessage({ + action: "UPDATE_STATE_REQUEST_BUY_ORDER", + payload: { + hostname, + crosschainAtInfo, + interactionId, + }, + }); + }, 500); + + // Store sendResponse callback with the interaction ID + pendingResponses.set(interactionId, sendResponse); + } + ); + + + }) + .catch((error) => { + console.error(error.message); + }); + } + + break; case "connection": { const { hostname } = request.payload; connection(hostname) @@ -964,7 +1029,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { console.error(error.message); }); } - + break; case "sendQort": { @@ -1097,34 +1162,34 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { } break; - case "buyOrderConfirmation": { - const { crosschainAtInfo, isDecline } = request.payload; - const interactionId2 = request.payload.interactionId; - // Retrieve the stored sendResponse callback - const originalSendResponse = pendingResponses.get(interactionId2); - - if (originalSendResponse) { - if (isDecline) { - originalSendResponse({ error: "User has declined" }); - sendResponse(false); - pendingResponses.delete(interactionId2); - return; - } - createBuyOrderTx({ crosschainAtInfo }) - .then((res) => { - sendResponse(true); - originalSendResponse(res); - pendingResponses.delete(interactionId2); - }) - .catch((error) => { - console.error(error.message); - sendResponse({ error: error.message }); - originalSendResponse({ error: error.message }); - }); - + case "buyOrderConfirmation": { + const { crosschainAtInfo, isDecline } = request.payload; + const interactionId2 = request.payload.interactionId; + // Retrieve the stored sendResponse callback + const originalSendResponse = pendingResponses.get(interactionId2); + + if (originalSendResponse) { + if (isDecline) { + originalSendResponse({ error: "User has declined" }); + sendResponse(false); + pendingResponses.delete(interactionId2); + return; } + createBuyOrderTx({ crosschainAtInfo }) + .then((res) => { + sendResponse(true); + originalSendResponse(res); + pendingResponses.delete(interactionId2); + }) + .catch((error) => { + console.error(error.message); + sendResponse({ error: error.message }); + originalSendResponse({ error: error.message }); + }); + } - + } + break; case "logout": @@ -1134,11 +1199,11 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { // Handle error console.error(chrome.runtime.lastError.message); } else { - chrome.tabs.query({}, function(tabs) { + chrome.tabs.query({}, function (tabs) { tabs.forEach(tab => { - chrome.tabs.sendMessage(tab.id, { type: "LOGOUT" }); + chrome.tabs.sendMessage(tab.id, { type: "LOGOUT" }); }); - }); + }); // Data removed successfully sendResponse(true); } diff --git a/src/transactions/signChat.ts b/src/transactions/signChat.ts index 20b2240..8814c2a 100644 --- a/src/transactions/signChat.ts +++ b/src/transactions/signChat.ts @@ -4,7 +4,6 @@ import nacl from '../deps/nacl-fast' import utils from '../utils/utils' export const signChat = (chatBytes, nonce, keyPair) => { - if (!chatBytes) { throw new Error('Chat Bytes not defined') } From 0399c9d1471a017742677e9dcfa6c7e5d13f5d65 Mon Sep 17 00:00:00 2001 From: Phillip Lang Martinez Date: Wed, 17 Jul 2024 17:00:43 -0400 Subject: [PATCH 3/6] get ltc balance --- public/content-script.js | 29 +++++++++++++++++++++++ src/App.tsx | 26 +++++++++++++++++++- src/background.ts | 51 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/public/content-script.js b/public/content-script.js index d3d2bd5..2fc77f7 100644 --- a/public/content-script.js +++ b/public/content-script.js @@ -121,6 +121,35 @@ document.addEventListener('qortalExtensionRequests', async (event) => { })); } }); + } else if(type === 'REQUEST_LTC_BALANCE'){ + + + const hostname = window.location.hostname + const res = await connection(hostname) + if(!res){ + document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { + detail: { type: "USER_INFO", data: { + error: "Not authorized" + }, requestId } + })); + return + } + chrome.runtime.sendMessage({ action: "ltcBalance", payload: { + hostname + }, timeout }, (response) => { + if (response.error) { + document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { + detail: { type: "LTC_BALANCE", data: { + error: response.error + }, requestId } + })); + } else { + // Include the requestId in the detail when dispatching the response + document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { + detail: { type: "LTC_BALANCE", data: response, requestId } + })); + } + }); } else if (type === 'REQUEST_AUTHENTICATION') { const hostname = window.location.hostname const res = await connection(hostname) diff --git a/src/App.tsx b/src/App.tsx index ccff2f9..ad40f94 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -56,6 +56,7 @@ type extStates = | "transfer-success-request" | "wallet-dropped" | "web-app-request-buy-order" + | "buy-order-submitted" ; function App() { @@ -366,7 +367,7 @@ function App() { }, (response) => { if (response === true) { - setExtstate("transfer-success-request"); + setExtstate("buy-order-submitted"); setCountdown(null); } else { @@ -1433,6 +1434,29 @@ function App() { )} + {extState === "buy-order-submitted" && ( + <> + + + + + Your buy order was submitted + + + { + window.close(); + }} + > + Close + + + )} {countdown && ( { // const validApi = await findUsableApi(); @@ -261,10 +282,13 @@ async function decryptWallet({ password, wallet, walletVersion }) { const wallet2 = new PhraseWallet(response, walletVersion); const keyPair = wallet2._addresses[0].keyPair; const ltcPrivateKey = wallet2._addresses[0].ltcWallet.derivedMasterPrivateKey + const ltcPublicKey = wallet2._addresses[0].ltcWallet.derivedMasterPublicKey + const toSave = { privateKey: Base58.encode(keyPair.privateKey), publicKey: Base58.encode(keyPair.publicKey), - ltcPrivateKey: ltcPrivateKey + ltcPrivateKey: ltcPrivateKey, + ltcPublicKey : ltcPublicKey } const dataString = JSON.stringify(toSave) await new Promise((resolve, reject) => { @@ -442,7 +466,9 @@ async function createBuyOrderTx({ crosschainAtInfo }) { signature: res?.signature, }) - return { atAddress: crosschainAtInfo.qortalAtAddress, chatSignature: res?.signature, node: buyTradeNodeBaseUrl } + return { atAddress: crosschainAtInfo.qortalAtAddress, chatSignature: res?.signature, node: buyTradeNodeBaseUrl, qortAddress: address } + } else { + throw new Error("Unable to send buy order message") } } catch (error) { @@ -522,12 +548,17 @@ function fetchMessages(apiCall) { }); } -function fetchMessagesForBuyOrders(apiCall, signature, senderPublicKey) { +async function fetchMessagesForBuyOrders(apiCall, signature, senderPublicKey) { let retryDelay = 2000; // Start with a 2-second delay const maxDuration = 360000 * 2; // Maximum duration set to 12 minutes const startTime = Date.now(); // Record the start time let triedChatMessage = [] // Promise to handle polling logic + await new Promise((res)=> { + setTimeout(() => { + res() + }, 40000); + }) return new Promise((resolve, reject) => { const attemptFetch = async () => { if (Date.now() - startTime > maxDuration) { @@ -734,6 +765,18 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { console.error(error.message); }); break; + case "ltcBalance": { + getLTCBalance() + .then((balance) => { + sendResponse(balance); + }) + .catch((error) => { + console.error(error.message); + }); + + + } + break; case "sendCoin": { const { receiver, password, amount } = request.payload; @@ -1184,7 +1227,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { .catch((error) => { console.error(error.message); sendResponse({ error: error.message }); - originalSendResponse({ error: error.message }); + // originalSendResponse({ error: error.message }); }); } From a743be7c0bdbc83b676177e64d9119aca6375d44 Mon Sep 17 00:00:00 2001 From: Phillip Lang Martinez Date: Wed, 17 Jul 2024 20:53:38 -0400 Subject: [PATCH 4/6] ltc section --- package-lock.json | 26 +++ package.json | 1 + src/App.tsx | 377 ++++++++++++++++++++++++++------------------ src/assets/ltc.png | Bin 0 -> 1480 bytes src/assets/qort.png | Bin 0 -> 1907 bytes src/background.ts | 9 +- 6 files changed, 257 insertions(+), 156 deletions(-) create mode 100644 src/assets/ltc.png create mode 100644 src/assets/qort.png diff --git a/package-lock.json b/package-lock.json index 8d577ee..5639f96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.16.4", "@mui/material": "^5.15.14", "@testing-library/jest-dom": "^6.4.6", "@testing-library/user-event": "^14.5.2", @@ -1221,6 +1222,31 @@ "url": "https://opencollective.com/mui-org" } }, + "node_modules/@mui/icons-material": { + "version": "5.16.4", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.4.tgz", + "integrity": "sha512-j9/CWctv6TH6Dou2uR2EH7UOgu79CW/YcozxCYVLJ7l03pCsiOlJ5sBArnWJxJ+nGkFwyL/1d1k8JEPMDR125A==", + "dependencies": { + "@babel/runtime": "^7.23.9" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/material": { "version": "5.15.14", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.14.tgz", diff --git a/package.json b/package.json index 67930ed..72083a2 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.16.4", "@mui/material": "^5.15.14", "@testing-library/jest-dom": "^6.4.6", "@testing-library/user-event": "^14.5.2", diff --git a/src/App.tsx b/src/App.tsx index ad40f94..77ff7b1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,14 +3,16 @@ import reactLogo from "./assets/react.svg"; import viteLogo from "/vite.svg"; import "./App.css"; import { useDropzone } from "react-dropzone"; -import { Box, Input, InputLabel, Tooltip, Typography } from "@mui/material"; +import { Box, CircularProgress, Input, InputLabel, Tooltip, Typography } from "@mui/material"; import { decryptStoredWallet } from "./utils/decryptWallet"; import { CountdownCircleTimer } from "react-countdown-circle-timer"; import Logo1 from "./assets/svgs/Logo1.svg"; import Logo1Dark from "./assets/svgs/Logo1Dark.svg"; - +import RefreshIcon from '@mui/icons-material/Refresh'; import Logo2 from "./assets/svgs/Logo2.svg"; import Copy from "./assets/svgs/Copy.svg"; +import ltcLogo from './assets/ltc.png'; +import qortLogo from './assets/qort.png' import { CopyToClipboard } from "react-copy-to-clipboard"; import Download from "./assets/svgs/Download.svg"; import Logout from "./assets/svgs/Logout.svg"; @@ -63,13 +65,16 @@ function App() { const [extState, setExtstate] = useState("not-authenticated"); const [backupjson, setBackupjson] = useState(null); const [rawWallet, setRawWallet] = useState(null); + const [ltcBalanceLoading, setLtcBalanceLoading] = useState(false); + const [qortBalanceLoading, setQortBalanceLoading] = useState(false) const [decryptedWallet, setdecryptedWallet] = useState(null); const [requestConnection, setRequestConnection] = useState(null); const [requestBuyOrder, setRequestBuyOrder] = useState(null); - + const [authenticatedMode, setAuthenticatedMode] = useState('qort') const [requestAuthentication, setRequestAuthentication] = useState(null); const [userInfo, setUserInfo] = useState(null); const [balance, setBalance] = useState(null); + const [ltcBalance, setLtcBalance] = useState(null) const [paymentTo, setPaymentTo] = useState(""); const [paymentAmount, setPaymentAmount] = useState(0); const [paymentPassword, setPaymentPassword] = useState(""); @@ -79,7 +84,7 @@ function App() { const [walletToBeDownloaded, setWalletToBeDownloaded] = useState(null); const [walletToBeDownloadedPassword, setWalletToBeDownloadedPassword] = useState(""); - const [authenticatePassword, setAuthenticatePassword] = + const [authenticatePassword, setAuthenticatePassword] = useState(""); const [sendqortState, setSendqortState] = useState(null); const [isLoading, setIsLoading] = useState(false) @@ -89,11 +94,11 @@ function App() { ] = useState(""); const [walletToBeDownloadedError, setWalletToBeDownloadedError] = useState(""); - const [walletToBeDecryptedError, setWalletToBeDecryptedError] = + const [walletToBeDecryptedError, setWalletToBeDecryptedError] = useState(""); - const holdRefExtState = useRef("not-authenticated") - useEffect(()=> { - if(extState){ + const holdRefExtState = useRef("not-authenticated") + useEffect(() => { + if (extState) { holdRefExtState.current = extState } }, [extState]) @@ -122,14 +127,14 @@ function App() { // Read the file as text reader.readAsText(file); }); - + let error: any = null; let pf: any; try { if (typeof fileContents !== "string") return; pf = JSON.parse(fileContents); - } catch (e) {} + } catch (e) { } try { const requiredFields = [ @@ -157,7 +162,7 @@ function App() { }, }); - + const saveWalletFunc = async (password: string) => { let wallet = structuredClone(rawWallet); @@ -167,7 +172,7 @@ function App() { wallet = await wallet2.generateSaveWalletData( password, crypto.kdfThreads, - () => {} + () => { } ); setWalletToBeDownloaded({ @@ -196,19 +201,30 @@ function App() { tabs[0].id, { from: "popup", subject: "anySubject" }, function (response) { - + } ); } ); }; - - + + const getBalanceFunc = () => { + setQortBalanceLoading(true) chrome.runtime.sendMessage({ action: "balance" }, (response) => { if (response && !response?.error) { setBalance(response); } + setQortBalanceLoading(false) + }); + }; + const getLtcBalanceFunc = () => { + setLtcBalanceLoading(true) + chrome.runtime.sendMessage({ action: "ltcBalance" }, (response) => { + if (response && !response?.error) { + setLtcBalance(response); + } + setLtcBalanceLoading(false) }); }; const sendCoinFunc = () => { @@ -257,7 +273,7 @@ function App() { useEffect(() => { // Listen for messages from the background script chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - + // Check if the message is to update the state if (message.action === "UPDATE_STATE_CONFIRM_SEND_QORT") { // Update the component state with the received 'sendqort' state @@ -284,7 +300,7 @@ function App() { }); }, []); - + //param = isDecline const confirmPayment = (isDecline: boolean) => { if (isDecline) { @@ -327,7 +343,7 @@ function App() { setExtstate("transfer-success-request"); setCountdown(null); } else { - + setSendPaymentError( response?.error || "Unable to perform payment. Please try again." ); @@ -349,7 +365,7 @@ function App() { }, }, (response) => { - window.close(); + window.close(); } ); return; @@ -361,8 +377,8 @@ function App() { action: "buyOrderConfirmation", payload: { crosschainAtInfo: requestBuyOrder?.crosschainAtInfo, - interactionId: requestBuyOrder?.interactionId, - isDecline: false, + interactionId: requestBuyOrder?.interactionId, + isDecline: false, }, }, (response) => { @@ -370,7 +386,7 @@ function App() { setExtstate("buy-order-submitted"); setCountdown(null); } else { - + setSendPaymentError( response?.error || "Unable to perform payment. Please try again." ); @@ -402,18 +418,18 @@ function App() { // useEffect(()=> { // rawWalletRef.current = rawWallet // }, [rawWallet]) - + useEffect(() => { try { setIsLoading(true) chrome.runtime.sendMessage({ action: "getWalletInfo" }, (response) => { if (response && response?.walletInfo) { setRawWallet(response?.walletInfo); - if(holdRefExtState.current === 'web-app-request-payment' || holdRefExtState.current === 'web-app-request-connection' || holdRefExtState.current === 'web-app-request-buy-order') return + if (holdRefExtState.current === 'web-app-request-payment' || holdRefExtState.current === 'web-app-request-connection' || holdRefExtState.current === 'web-app-request-buy-order') return setExtstate("authenticated"); } }); - } catch (error) {} finally { + } catch (error) { } finally { setIsLoading(false) } }, []); @@ -427,7 +443,8 @@ function App() { } }); getBalanceFunc(); - } catch (error) {} + getLtcBalanceFunc() + } catch (error) { } }, [address]); useEffect(() => { @@ -444,8 +461,8 @@ function App() { return; } setIsLoading(true) - await new Promise((res)=> { - setTimeout(()=> { + await new Promise((res) => { + setTimeout(() => { res() }, 250) }) @@ -487,8 +504,8 @@ function App() { return; } setIsLoading(true) - await new Promise((res)=> { - setTimeout(()=> { + await new Promise((res) => { + setTimeout(() => { res() }, 250) }) @@ -496,12 +513,14 @@ function App() { const wallet = await res.generateSaveWalletData( walletToBeDownloadedPassword, crypto.kdfThreads, - () => {} + () => { } ); - chrome.runtime.sendMessage({ action: "decryptWallet", payload: { - password: walletToBeDownloadedPassword, - wallet - } }, (response) => { + chrome.runtime.sendMessage({ + action: "decryptWallet", payload: { + password: walletToBeDownloadedPassword, + wallet + } + }, (response) => { if (response && !response?.error) { setRawWallet(wallet); setWalletToBeDownloaded({ @@ -515,18 +534,18 @@ function App() { } }); getBalanceFunc(); - } else if(response?.error){ + } else if (response?.error) { setIsLoading(false) setWalletToBeDecryptedError(response.error) } }); - - + + } catch (error: any) { setWalletToBeDownloadedError(error?.message); setIsLoading(false) - } + } }; const logoutFunc = () => { @@ -536,7 +555,7 @@ function App() { resetAllStates(); } }); - } catch (error) {} + } catch (error) { } }; const returnToMain = () => { @@ -561,6 +580,7 @@ function App() { setRequestAuthentication(null); setUserInfo(null); setBalance(null); + setLtcBalance(null) setPaymentTo(""); setPaymentAmount(0); setPaymentPassword(""); @@ -574,19 +594,21 @@ function App() { setSendqortState(null); }; - const authenticateWallet = async()=> { + const authenticateWallet = async () => { try { setIsLoading(true) setWalletToBeDecryptedError('') - await new Promise((res)=> { - setTimeout(()=> { + await new Promise((res) => { + setTimeout(() => { res() }, 250) }) - chrome.runtime.sendMessage({ action: "decryptWallet", payload: { - password: authenticatePassword, - wallet: rawWallet - } }, (response) => { + chrome.runtime.sendMessage({ + action: "decryptWallet", payload: { + password: authenticatePassword, + wallet: rawWallet + } + }, (response) => { if (response && !response?.error) { setAuthenticatePassword(""); setExtstate("authenticated"); @@ -598,14 +620,14 @@ function App() { } }); getBalanceFunc(); - } else if(response?.error){ + } else if (response?.error) { setIsLoading(false) setWalletToBeDecryptedError(response.error) } }); } catch (error) { setWalletToBeDecryptedError('Unable to authenticate. Wrong password') - } + } } return ( @@ -684,55 +706,91 @@ function App() { - - - - {userInfo?.name} - - - - - {rawWallet?.address0?.slice(0, 6)}... - {rawWallet?.address0?.slice(-4)} - - - - {balance && ( - - {balance?.toFixed(2)} QORT - - )} - - {/*

balance: {balance}

*/} - - {/* { - setExtstate("download-wallet"); - }} - > - Download Wallet - */} - { - setExtstate("send-qort"); - }} - > - Transfer QORT - + {authenticatedMode === 'ltc' ? ( + <> + + + + + {rawWallet?.ltcAddress?.slice(0, 6)}... + {rawWallet?.ltcAddress?.slice(-4)} + + + + {ltcBalanceLoading && } + {ltcBalance && !ltcBalanceLoading && ( + + + {ltcBalance?.toFixed(5)} LTC + + + + + )} + + + ) : ( + <> + + + + {userInfo?.name} + + + + + {rawWallet?.address0?.slice(0, 6)}... + {rawWallet?.address0?.slice(-4)} + + + + {qortBalanceLoading && } + {balance && ( + + {balance?.toFixed(2)} QORT + + )} + + + { + setExtstate("send-qort"); + }} + > + Transfer QORT + + + )} +
@@ -753,6 +811,25 @@ function App() { cursor: "pointer", }} /> + + {authenticatedMode === 'qort' && ( + { + setAuthenticatedMode('ltc') + }} src={ltcLogo} style={{ + cursor: "pointer", + width: '20px', + height: 'auto' + }} /> + )} + {authenticatedMode === 'ltc' && ( + { + setAuthenticatedMode('qort') + }} src={qortLogo} style={{ + cursor: "pointer", + width: '20px', + height: 'auto' + }} /> + )}
)} @@ -865,7 +942,7 @@ function App() { )} - {extState === "web-app-request-buy-order" && ( + {extState === "web-app-request-buy-order" && ( <> @@ -1143,7 +1220,7 @@ function App() { style={{ cursor: "pointer", }} - onClick={()=> { + onClick={() => { setRawWallet(null); setExtstate("not-authenticated"); }} @@ -1178,32 +1255,32 @@ function App() {
- - <> - - Wallet Password - - - - setAuthenticatePassword(e.target.value) + + <> + + Wallet Password + + + + setAuthenticatePassword(e.target.value) + } + onKeyDown={(e) => { + if (e.key === "Enter") { + authenticateWallet(); } - onKeyDown={(e) => { - if (e.key === "Enter") { - authenticateWallet(); - } - }} - /> - - - Authenticate - - - {walletToBeDecryptedError} - - + }} + /> + + + Authenticate + + + {walletToBeDecryptedError} + + )} {extState === "download-wallet" && ( @@ -1290,34 +1367,34 @@ function App() { <> {!walletToBeDownloaded && ( <> - - - { - setExtstate("not-authenticated") - }} - src={Return} - /> - + + + { + setExtstate("not-authenticated") + }} + src={Return} + /> +
- - -
+ width: '136px', + height: '154px' + }}> + + + 1|2yZCj9+5J-Mgy_cW;hN+Bi?AdDfwAD>0I zCWX7$us|t9(7Gu)5)9fWNNJJB(GWl>$RvUsB5EazVAH+?iXc;TbQ8TK57#9N ztZ<+dB>2U&h9I55CMj4`OQ0C70pvLGmnAbZlDzAG(9{b zjUjUsYElVMG;vdd=q)1~i1WGtE})oGF8Uzw)Dy50Jh`iDpa|3SvA=ZVSL1G>x&j&? znA}jAfd&vJ7gQ#o7=Sh!*k`i%)F?}!G8jM53@}WBzn!U~G8nWo-GHePj|^eX&2REp z0WE|qOXPO7imD<|^qaW=1|gOp)leni0gA!GC5VIpJs~k*lG7XMFP!i zJKN&=yv)}fWvLXhs;QwV6<0UhAK^N0T^&$IR)!ARS=3Osvitso9m3nC-1Cr1#P}znc4L^@I#hH8ig3O2%S6eoU8~B(4Qep6Cwc=GM1e zJ%M6ZNm;*hn4{dxY-*>S#c1CN8X8q~1(EaZx-__L(sPmzHm%F&y zb(PN1BriSjp-K{ZUjJFi4eb2d*CN3;l$kqc|I@h){h>{mZwR0|m7&clz7!&Ky#n2) zl}w(z`|w4ps>w5;kS~JYoVYQ_mCP4qbv;ksuWgZ)^}LpueX|#)eLoSa+r%L)dh+U{ z>PH^xedq+OVnp~$&`8~3dIFRudG}fUUIw%v)7*KIcc%cPVY|*r3IJ5vqS9eOsS!kT zpU4IP-IJ-IR^gq8@c0>pWmD<&S#lQ#?NzE9k10fn^5q~n?BPi|{H zb281)s$FBTE3Q8WZipszI*XXivNRMR*g^Pgb{(G&#hJKS+26$5uJe((XMtt0QUbG= z`k;w4 z+z5*;2#MVYE5K0YZPgY)sPZ=xv_TEGyA%Z!p@ti>iUeu_o2G|*4FYq9f~;%bfN)c5 zOF5~zj2~H$H?da0000|H@`8$}eJETAGPXj)W25R?Ukkb0?o>#feEH&j>ZjYC{+oG`zj+g}jp#Eota zy;VvgZoPDK>n+ZKNC>sqA|RlG+EhhU2*P~ZnP9uS_Uz1?nT@qiS~*_N&c6Nf=Dj!H zyxmOGX0pw+NykuJdOOhP~gi*V948deV=bpF*|A2Bu zI{ahPVdS|i#QPO|PN@24C7cI;P^qL!*`p{~IUUweslF22PsfAH#fj&uJpYw!Hb zdVl;~N=sD&R0N=|i)4NA#0#ulKf|tHI6Jl7je8GSXZLqDc>0g4NkB~{P^k|2#08kG zbBpQFo&_cZZF--6m>%OmmIVgp7AkVHRoi|t;s9M-dPVE_or6DF#4R>Ccw!sab#A3? zZ9v8BVbi9c1dyvlR<^M}NP)HNU$0E#S#tpuezRvQ2m#1;{dIQgL_pf_@aezYxBiv_ zYcWWLdH!_T)xI(cT(yZ>ozo@%j$)f&(Db`7r2E~`&X1hFky9N`2bK>040lE-MtYo2rX}?KvM&y&x zg*U7WZC(Ew-TO7oUy0<}B=esIXwNj~L>dH(Smsf@jMC>>K|)}u7X02Om~Zw&l8 zPh$Y2PCC;O$F9?*LRtncS52jbHhgG-DEl4<1HQ|I*Tk{t1AU=SPtx!PDE#CV?gtyU zdrBz_BCtvnMqlU?efQll`eT4Du(fZ%Dm~Keg-185Oc1h&ND$6Au1FNtVYK+rH39WB z05u{)iU4~0%Wsv3b{^?mluU+YKlqK~pz=)9NI zI8E?_3IkfH0IdM60IdM6qyn@8be;hnRxpss5P)`lYXM5nguleG=%WPE1)u}pT5R2a ze6&nlWPJ#2kn*;M&Ul*!a@J;+=i!an?l zHjcSY6-k_0Xc7Jr<%d`wy2gQnkHm(*vCl$jBAE|OD@PVO``UNK6iwJAbb2gHJOJdF8__-FE>AamnB!jv=CRt4U}+rKR4V*6Z^!62C^8|uvjZS zwgim*|A30S8oVX=pV!Z%{uKqfMWuaotXIZh&?#zHxYVN)?F>Q%MQJPU=isLm>jVe`li7eZ0gtBO&O6BUv5Z#4b_PsMTa6fs#(uR5iKFD_blZy;kcA}tFYOoj)L5TK2SiV3_hsM%L4(yxH$dR32Ts%rh8_dFh`U2Hs zdSv%uf+F&FUHMdx($@h~%R)yiwR#{cC7>g~f*@F(6EJ5!ip@Hp0#w}E93Nx9IutV? z<8?I>)>bGIQWGkU1UiRQ?+vM53{ch=17rXTVkKhLt1GMkTfJPx0 txTCL73&mFC6>0 { chrome.storage.local.set({ walletInfo: newWallet }, () => { @@ -322,7 +322,6 @@ async function decryptWallet({ password, wallet, walletVersion }) { } async function signChatFunc(chatBytesArray, chatNonce, validApi, keyPair) { - console.log({ chatBytesArray, chatNonce, validApi, keyPair }) let response try { const signedChatBytes = signChat( @@ -569,7 +568,6 @@ async function fetchMessagesForBuyOrders(apiCall, signature, senderPublicKey) { const response = await fetch(apiCall); let data = await response.json(); data = data.filter((item) => !triedChatMessage.includes(item.signature)) - console.log({data}) if (data && data.length > 0) { const encodedMessageObj = data[0] const resKeyPair = await getKeyPair() @@ -583,7 +581,6 @@ async function fetchMessagesForBuyOrders(apiCall, signature, senderPublicKey) { const decodedMessage = decryptChatMessage(encodedMessageObj.data, keyPair.privateKey, senderPublicKey, encodedMessageObj.reference) const parsedMessage = JSON.parse(decodedMessage) - console.log({parsedMessage}) if (parsedMessage?.extra?.chatRequestSignature === signature) { resolve(parsedMessage); } else { From 97775507410f741cfb9b427bf81f9eeea321bffb Mon Sep 17 00:00:00 2001 From: Phillip Lang Martinez Date: Thu, 18 Jul 2024 00:05:45 -0400 Subject: [PATCH 5/6] fix tofixed error --- src/App.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 77ff7b1..4e4d9a9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -719,7 +719,7 @@ function App() { {ltcBalanceLoading && } - {ltcBalance && !ltcBalanceLoading && ( + {ltcBalance && !isNaN(+ltcBalance) && !ltcBalanceLoading && ( - {ltcBalance?.toFixed(5)} LTC + {ltcBalance} LTC - {balance?.toFixed(2)} QORT + {balance} QORT )} @@ -891,7 +891,7 @@ function App() { fontWeight: 700, }} > - {balance?.toFixed(2)} QORT + {balance} QORT @@ -960,18 +960,19 @@ function App() { - {requestBuyOrder?.crosschainAtInfo?.qortAmount} QORT + {+requestBuyOrder?.crosschainAtInfo?.qortAmount} QORT FOR From 5d745f734d974b12a056145169a6944a908b1418 Mon Sep 17 00:00:00 2001 From: Phillip Lang Martinez Date: Thu, 18 Jul 2024 11:22:51 -0400 Subject: [PATCH 6/6] change position of fetch balance --- src/App.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index 4e4d9a9..a01a4eb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -443,7 +443,6 @@ function App() { } }); getBalanceFunc(); - getLtcBalanceFunc() } catch (error) { } }, [address]); @@ -453,6 +452,12 @@ function App() { }; }, []); + useEffect(()=> { + if(authenticatedMode === 'ltc' && !ltcBalanceLoading && ltcBalance === null ){ + getLtcBalanceFunc() + } + }, [authenticatedMode]) + const confirmPasswordToDownload = async () => { try { setWalletToBeDownloadedError("");