diff --git a/electron/src/setup.ts b/electron/src/setup.ts index acb74cf..c489bc0 100644 --- a/electron/src/setup.ts +++ b/electron/src/setup.ts @@ -15,23 +15,23 @@ import { myCapacitorApp } from '.'; const defaultDomains = [ + 'capacitor-electron://-', 'http://127.0.0.1:12391', 'ws://127.0.0.1:12391', 'https://ext-node.qortal.link', - 'wss://ext-node.qortal.link', - 'https://appnode.qortal.org', - 'wss://appnode.qortal.org', - "https://api.qortal.org", - "https://api2.qortal.org", - "https://appnode.qortal.org", - "https://apinode.qortalnodes.live", + 'wss://ext-node.qortal.link', + 'https://appnode.qortal.org', + 'wss://appnode.qortal.org', + "https://api.qortal.org", + "https://api2.qortal.org", + "https://apinode.qortalnodes.live", "https://apinode1.qortalnodes.live", "https://apinode2.qortalnodes.live", "https://apinode3.qortalnodes.live", "https://apinode4.qortalnodes.live", - "https://www.qort.trade" - + "https://www.qort.trade" ]; + // let allowedDomains: string[] = [...defaultDomains] const domainHolder = { allowedDomains: [...defaultDomains], @@ -144,9 +144,7 @@ export class ElectronCapacitorApp { contextIsolation: true, // Use preload to inject the electron varriant overrides for capacitor plugins. // preload: join(app.getAppPath(), "node_modules", "@capacitor-community", "electron", "dist", "runtime", "electron-rt.js"), - preload: preloadPath, - webSecurity: false - }, + preload: preloadPath }, }); this.mainWindowState.manage(this.MainWindow); @@ -242,58 +240,100 @@ export class ElectronCapacitorApp { } } -// Set a CSP up for our application based on the custom scheme -// export function setupContentSecurityPolicy(customScheme: string): void { -// session.defaultSession.webRequest.onHeadersReceived((details, callback) => { -// callback({ -// responseHeaders: { -// ...details.responseHeaders, -// 'Content-Security-Policy': [ -// "script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval'; object-src 'self'; connect-src 'self' https://*:* http://*:* wss://*:* ws://*:*", -// ], -// }, -// }); -// }); -// } + export function setupContentSecurityPolicy(customScheme: string): void { - session.defaultSession.webRequest.onHeadersReceived((details, callback) => { - const allowedSources = ["'self'", ...domainHolder.allowedDomains].join(' '); - const csp = ` - script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval' ${allowedSources}; - object-src 'self'; - connect-src ${allowedSources}; - `.replace(/\s+/g, ' ').trim(); + session.defaultSession.webRequest.onHeadersReceived((details: any, callback) => { + const allowedSources = ["'self'", customScheme, ...domainHolder.allowedDomains]; + const connectSources = [...allowedSources]; + const frameSources = [ + "'self'", + 'http://localhost:*', + 'https://localhost:*', + 'http://127.0.0.1:*', + 'https://127.0.0.1:*', + ...allowedSources, + ]; - callback({ - responseHeaders: { - ...details.responseHeaders, - 'Content-Security-Policy': [csp], - }, - }); - }); - + // Create the Content Security Policy (CSP) string + const csp = ` + default-src 'self' ${allowedSources.join(' ')}; + frame-src ${frameSources.join(' ')}; + script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval' ${allowedSources.join(' ')}; + object-src 'self'; + connect-src ${connectSources.join(' ')}; + img-src 'self' data: blob: ${allowedSources.join(' ')}; + style-src 'self' 'unsafe-inline'; + font-src 'self' data:; + `.replace(/\s+/g, ' ').trim(); + + // Get the request URL and origin + const requestUrl = details.url; + const requestOrigin = details.origin || details.referrer || 'capacitor-electron://-'; + + // Parse the request URL to get its origin + let requestUrlOrigin: string; + try { + const parsedUrl = new URL(requestUrl); + requestUrlOrigin = parsedUrl.origin; + } catch (e) { + // Handle invalid URLs gracefully + requestUrlOrigin = ''; + } + + // Determine if the request is cross-origin + const isCrossOrigin = requestOrigin !== requestUrlOrigin; + + // Check if the response already includes Access-Control-Allow-Origin + const hasAccessControlAllowOrigin = Object.keys(details.responseHeaders).some( + (header) => header.toLowerCase() === 'access-control-allow-origin' + ); + + // Prepare response headers + const responseHeaders: Record = { + ...details.responseHeaders, + 'Content-Security-Policy': [csp], + }; + + if (isCrossOrigin && !hasAccessControlAllowOrigin) { + // Handle CORS for cross-origin requests lacking CORS headers + // Optionally, check if the requestOrigin is allowed + responseHeaders['Access-Control-Allow-Origin'] = requestOrigin; + responseHeaders['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS, DELETE'; + responseHeaders['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, x-api-key'; + } + + // Callback with modified headers + callback({ responseHeaders }); + }); } + + + + + + + // IPC listener for updating allowed domains ipcMain.on('set-allowed-domains', (event, domains: string[]) => { // Validate and transform user-provided domains const validatedUserDomains = domains - .flatMap((domain) => { - try { - const url = new URL(domain); - const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'; - const socketUrl = `${protocol}//${url.hostname}${url.port ? ':' + url.port : ''}`; - return [url.origin, socketUrl]; - } catch { - return []; - } - }) - .filter(Boolean) as string[]; + .flatMap((domain) => { + try { + const url = new URL(domain); + const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'; + const socketUrl = `${protocol}//${url.hostname}${url.port ? ':' + url.port : ''}`; + return [url.origin, socketUrl]; + } catch { + return []; + } + }) + .filter(Boolean) as string[]; // Combine default and validated user domains const newAllowedDomains = [...new Set([...defaultDomains, ...validatedUserDomains])]; diff --git a/src/App.tsx b/src/App.tsx index 5987ae0..26f66ed 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2744,6 +2744,25 @@ await showInfo({ )} + {messageQortalRequestExtension?.foreignFee && ( + <> + + + + {"Foreign Fee: "} + {messageQortalRequestExtension?.foreignFee} + + + + )} {messageQortalRequestExtension?.checkbox1 && ( { }); }; +export const getForeignKey = async (foreignBlockchain)=> { + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + + switch (foreignBlockchain) { + case "LITECOIN": + return parsedData.ltcPrivateKey + + default: + return null + } +} + export const pauseAllQueues = () => controlAllQueues("pause"); export const resumeAllQueues = () => controlAllQueues("resume"); const checkDifference = (createdTimestamp) => { @@ -1657,27 +1673,29 @@ export async function decryptDirectFunc({ messages, involvingAddress }) { return holdMessages; } -export async function createBuyOrderTx({ crosschainAtInfo, useLocal }) { +export async function createBuyOrderTx({ crosschainAtInfo, isGateway, foreignBlockchain }) { try { - if (useLocal) { + + if (!isGateway) { const wallet = await getSaveWallet(); const address = wallet.address0; - const resKeyPair = await getKeyPair(); - const parsedData = resKeyPair; const message = { addresses: crosschainAtInfo.map((order)=> order.qortalAtAddress), - foreignKey: parsedData.ltcPrivateKey, + foreignKey: await getForeignKey(foreignBlockchain), receivingAddress: address, }; let responseVar; const txn = new TradeBotRespondMultipleRequest().createTransaction( message ); - const apiKey = await getApiKeyFromStorage(); + + + const url = await createEndpoint('/crosschain/tradebot/respondmultiple') + const responseFetch = await fetch( - `http://127.0.0.1:12391/crosschain/tradebot/respondmultiple?apiKey=${apiKey?.apikey}`, + url, { method: "POST", headers: { @@ -1706,7 +1724,7 @@ export async function createBuyOrderTx({ crosschainAtInfo, useLocal }) { message: "Transaction processed successfully!", atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress), senderAddress: address, - node: 'http://127.0.0.1:12391' + node: url }, }; } else { @@ -1716,7 +1734,7 @@ export async function createBuyOrderTx({ crosschainAtInfo, useLocal }) { message: response, atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress), senderAddress: address, - node: 'http://127.0.0.1:12391' + node: url }, }; } @@ -1726,11 +1744,10 @@ export async function createBuyOrderTx({ crosschainAtInfo, useLocal }) { const wallet = await getSaveWallet(); const address = wallet.address0; - const resKeyPair = await getKeyPair(); - const parsedData = resKeyPair; + const message = { addresses: crosschainAtInfo.map((order)=> order.qortalAtAddress), - foreignKey: parsedData.ltcPrivateKey, + foreignKey: await getForeignKey(foreignBlockchain), receivingAddress: address, }; const res = await sendChatForBuyOrder({ @@ -1742,16 +1759,15 @@ export async function createBuyOrderTx({ crosschainAtInfo, useLocal }) { if (res?.signature) { - let responseMessage; - - if(res?.encryptedMessageToBase58){ + const message = await listenForChatMessageForBuyOrder({ nodeBaseUrl: buyTradeNodeBaseUrl, senderAddress: proxyAccountAddress, senderPublicKey: proxyAccountPublicKey, signature: res?.signature, }); - responseMessage = { + + const responseMessage = { callResponse: message.callResponse, extra: { message: message?.extra?.message, @@ -1760,48 +1776,7 @@ export async function createBuyOrderTx({ crosschainAtInfo, useLocal }) { atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress), } } - } else { - const message = await listenForChatMessageForBuyOrder({ - nodeBaseUrl: buyTradeNodeBaseUrl, - senderAddress: proxyAccountAddress, - senderPublicKey: proxyAccountPublicKey, - signature: res?.signature, - }); - - responseMessage = { - callResponse: message.callResponse, - extra: { - message: message?.extra?.message, - senderAddress: address, - node: buyTradeNodeBaseUrl, - atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress), - } - } - } - - - // 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, - // }; - - + return responseMessage } else { throw new Error("Unable to send buy order message"); diff --git a/src/components/Apps/AppsDevModeHome.tsx b/src/components/Apps/AppsDevModeHome.tsx index a5b4df1..6fe8787 100644 --- a/src/components/Apps/AppsDevModeHome.tsx +++ b/src/components/Apps/AppsDevModeHome.tsx @@ -36,7 +36,7 @@ export const AppsDevModeHome = ({ availableQapps, }) => { - const [domain, setDomain] = useState(""); + const [domain, setDomain] = useState("127.0.0.1"); const [port, setPort] = useState(""); const { isShow, onCancel, onOk, show, message } = useModal(); const { diff --git a/src/components/Apps/useQortalMessageListener.tsx b/src/components/Apps/useQortalMessageListener.tsx index 177d50d..52a69fa 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', 'CREATE_TRADE_BUY_ORDER', 'CREATE_TRADE_SELL_ORDER' + 'ADD_FOREIGN_SERVER', 'REMOVE_FOREIGN_SERVER', 'GET_DAY_SUMMARY', 'CREATE_TRADE_BUY_ORDER', 'CREATE_TRADE_SELL_ORDER', 'CANCEL_TRADE_SELL_ORDER', 'IS_USING_GATEWAY' ]; @@ -437,7 +437,7 @@ isDOMContentLoaded: false if (event?.data?.requestedHandler !== 'UI') return; const sendMessageToRuntime = (message, eventPort) => { - window.sendMessage(message.action, message.payload, 60000, message.isExtension) + window.sendMessage(message.action, message.payload, 300000, message.isExtension) .then((response) => { if (response.error) { eventPort.postMessage({ diff --git a/src/components/Save/Save.tsx b/src/components/Save/Save.tsx index 5700eb9..0081f0f 100644 --- a/src/components/Save/Save.tsx +++ b/src/components/Save/Save.tsx @@ -83,9 +83,9 @@ export const Save = ({ isDesktop, disableWidth }) => { .sendMessage( "ENCRYPT_DATA", { - payload: { + data64, - }, + }, 60000 ) @@ -101,6 +101,7 @@ export const Save = ({ isDesktop, disableWidth }) => { console.error("Failed qortalRequest", error); }); }); + console.log('encryptData', encryptData) if (encryptData && !encryptData?.error) { const fee = await getFee("ARBITRARY"); @@ -138,6 +139,7 @@ export const Save = ({ isDesktop, disableWidth }) => { } } } catch (error) { + console.log('errorsave', error) setInfoSnack({ type: "error", message: error?.message || "Unable to save to QDN", diff --git a/src/qortalRequests.ts b/src/qortalRequests.ts index be6f2c8..9140b6b 100644 --- a/src/qortalRequests.ts +++ b/src/qortalRequests.ts @@ -1,4 +1,5 @@ -import { addForeignServer, addListItems, createBuyOrder, createPoll, createSellOrder, 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 { gateways, getApiKeyFromStorage } from "./background"; +import { addForeignServer, addListItems, cancelSellOrder, createBuyOrder, createPoll, createSellOrder, 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"; @@ -17,6 +18,15 @@ function setLocalStorage(key, data) { throw error; }); } +export const isRunningGateway = async ()=> { + let isGateway = true; + const apiKey = await getApiKeyFromStorage(); + if (apiKey && (apiKey?.url && !gateways.some(gateway => apiKey?.url?.includes(gateway)))) { + isGateway = false; + } + + return isGateway +} export async function setPermission(key, value) { @@ -623,7 +633,47 @@ function setLocalStorage(key, data) { } break; } - + case "CANCEL_TRADE_SELL_ORDER": { + try { + const res = await cancelSellOrder(request.payload, isFromExtension); + event.source.postMessage({ + requestId: request.requestId, + action: request.action, + payload: res, + type: "backgroundMessageResponse", + }, event.origin); + } catch (error) { + event.source.postMessage({ + requestId: request.requestId, + action: request.action, + error: error.message, + type: "backgroundMessageResponse", + }, event.origin); + } + break; + } + case "IS_USING_GATEWAY": { + try { + console.log('isusing going') + let isGateway = await isRunningGateway() + console.log('isGateway', isGateway) + event.source.postMessage({ + requestId: request.requestId, + action: request.action, + payload: {isGateway}, + type: "backgroundMessageResponse", + }, event.origin); + } catch (error) { + console.log('isusing going', 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 3936f5c..677b102 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -28,11 +28,19 @@ import { uint8ArrayToBase64, } from "../qdn/encryption/group-encryption"; import { publishData } from "../qdn/publish/pubish"; -import { getPermission, setPermission } from "../qortalRequests"; +import { getPermission, isRunningGateway, setPermission } from "../qortalRequests"; +import TradeBotCreateRequest from "../transactions/TradeBotCreateRequest"; +import DeleteTradeOffer from "../transactions/TradeBotDeleteRequest"; +import signTradeBotTransaction from "../transactions/signTradeBotTransaction"; import { createTransaction } from "../transactions/transactions"; import { mimeToExtensionMap } from "../utils/memeTypes"; - +const sellerForeignFee = { + 'LITECOIN': { + value: '~0.00005', + ticker: 'LTC' + } +} const btcFeePerByte = 0.00000100 const ltcFeePerByte = 0.00000030 @@ -302,7 +310,6 @@ export const encryptData = async (data, sender) => { if (!data64) { throw new Error("Please include data to encrypt"); } - const resKeyPair = await getKeyPair(); const parsedData = resKeyPair; const privateKey = parsedData.privateKey; @@ -1367,7 +1374,6 @@ export const getWalletBalance = async (data, bypassPermission?: boolean, isFromE } const value = (await getPermission(`qAPPAutoWalletBalance-${data.coin}`)) || false; - console.log('value', value) let skip = false; if (value) { skip = true; @@ -1384,7 +1390,6 @@ export const getWalletBalance = async (data, bypassPermission?: boolean, isFromE }, }, isFromExtension); } - console.log('resPermission', resPermission) const { accepted = false, checkbox1 = false } = resPermission || {}; if(resPermission){ setPermission(`qAPPAutoWalletBalance-${data.coin}`, checkbox1); @@ -1476,7 +1481,7 @@ export const getWalletBalance = async (data, bypassPermission?: boolean, isFromE } }; -const getUserWalletFunc = async (coin) => { +export const getUserWalletFunc = async (coin) => { let userWallet = {}; const wallet = await getSaveWallet(); const address = wallet.address0; @@ -2413,7 +2418,7 @@ export const createBuyOrder = async (data, isFromExtension) => { const requiredFields = [ "crosschainAtInfo", - "processType" + "foreignBlockchain" ]; const missingFields: string[] = []; requiredFields.forEach((field) => { @@ -2426,12 +2431,10 @@ export const createBuyOrder = async (data, isFromExtension) => { const errorMsg = `Missing fields: ${missingFieldsString}`; throw new Error(errorMsg); } + const isGateway = await isRunningGateway() + const foreignBlockchain = data.foreignBlockchain 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({ @@ -2448,16 +2451,18 @@ export const createBuyOrder = async (data, isFromExtension) => { }, 0) )} ${` ${crosschainAtInfo?.[0]?.foreignBlockchain}`}`, - highlightedText: `Using ${processType}`, - fee: '' + highlightedText: `Is using gateway: ${isGateway}`, + fee: '', + foreignFee: `${sellerForeignFee[foreignBlockchain].value} ${sellerForeignFee[foreignBlockchain].ticker}` }, isFromExtension); const { accepted } = resPermission; if (accepted) { const resBuyOrder = await createBuyOrderTx( { crosschainAtInfo, - useLocal: processType === 'local' ? true : false - } + isGateway, + foreignBlockchain + } ); return resBuyOrder; } else { @@ -2468,11 +2473,142 @@ export const createBuyOrder = async (data, isFromExtension) => { } }; + const cancelTradeOfferTradeBot = async (body, keyPair) => { + const txn = new DeleteTradeOffer().createTransaction(body) + const url = await createEndpoint(`/crosschain/tradeoffer`); + const bodyToString = JSON.stringify(txn); + + const deleteTradeBotResponse = await fetch(url, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + body: bodyToString, + }); + + if(!deleteTradeBotResponse.ok) throw new Error('Unable to update tradebot') + const unsignedTxn = await deleteTradeBotResponse.text() + const signedTxnBytes = await signTradeBotTransaction( + unsignedTxn, + keyPair + ) + const signedBytes = Base58.encode(signedTxnBytes); + + let res + try { + res = await processTransactionVersion2(signedBytes) + } catch (error) { + return { + error: "Failed to Cancel Sell Order. Try again!", + failedTradeBot: { + atAddress: body.atAddress, + creatorAddress: body.creatorAddress + } + } + } + if(res?.error){ + return { + error: "Failed to Cancel Sell Order. Try again!", + failedTradeBot: { + atAddress: body.atAddress, + creatorAddress: body.creatorAddress + } + } + } + if (res?.signature){ + return res + } else { + throw new Error("Failed to Cancel Sell Order. Try again!") + } +} +const findFailedTradebot = async (createBotCreationTimestamp, body)=> { + //wait 5 secs + const wallet = await getSaveWallet(); + const address = wallet.address0; + await new Promise((res)=> { + setTimeout(() => { + res(null) + }, 5000); + }) + const url = await createEndpoint(`/crosschain/tradebot?foreignBlockchain=LITECOIN`); + + const tradeBotsReponse = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + const data = await tradeBotsReponse.json() + const latestItem2 = data + .filter( + (item) => + item.creatorAddress === address + ).sort((a, b) => b.timestamp - a.timestamp)[0] + const latestItem = data + .filter( + (item) => + item.creatorAddress === address && + +item.foreignAmount === +body.foreignAmount + ) + .sort((a, b) => b.timestamp - a.timestamp)[0]; + if ( + latestItem && + createBotCreationTimestamp - latestItem.timestamp <= 5000 && + createBotCreationTimestamp > latestItem.timestamp // Ensure latestItem's timestamp is before createBotCreationTimestamp + ) { + + return latestItem + } else { + return null + } + +} +const tradeBotCreateRequest = async (body, keyPair)=> { + const txn = new TradeBotCreateRequest().createTransaction(body) + const url = await createEndpoint(`/crosschain/tradebot/create`); + const bodyToString = JSON.stringify(txn); + + const unsignedTxnResponse = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: bodyToString, + }); + if(!unsignedTxnResponse.ok) throw new Error('Unable to create tradebot') + const createBotCreationTimestamp = Date.now() + const unsignedTxn = await unsignedTxnResponse.text() + const signedTxnBytes = await signTradeBotTransaction( + unsignedTxn, + keyPair + ) + const signedBytes = Base58.encode(signedTxnBytes); + + let res + try { + res = await processTransactionVersion2(signedBytes) + } catch (error) { + const findFailedTradeBot = await findFailedTradebot(createBotCreationTimestamp, body) + return { + error: "Failed to Create Sell Order. Try again!", + failedTradeBot: findFailedTradeBot + } + } + + if (res?.signature){ + return res + } else { + throw new Error("Failed to Create Sell Order. Try again!") + } + +} + export const createSellOrder = async (data, isFromExtension) => { const requiredFields = [ - "crosschainAtInfo", - "processType" + "qortAmount", + "foreignBlockchain", + "foreignAmount" ]; const missingFields: string[] = []; requiredFields.forEach((field) => { @@ -2485,44 +2621,99 @@ export const createSellOrder = async (data, isFromExtension) => { 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') - } +const receivingAddress = await getUserWalletFunc('LTC') 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: '' + text1: "Do you give this application permission to perform a sell order?", + text2: `${data.qortAmount}${" "} + ${`QORT`}`, + text3: `FOR ${data.foreignAmount} ${data.foreignBlockchain}`, + fee: '0.02' }, isFromExtension); const { accepted } = resPermission; if (accepted) { - const resBuyOrder = await createBuyOrderTx( - { - crosschainAtInfo, - useLocal: processType === 'local' ? true : false - } - ); - return resBuyOrder; + const resKeyPair = await getKeyPair() + const parsedData = resKeyPair + const userPublicKey = parsedData.publicKey + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + const response = await tradeBotCreateRequest({ + creatorPublicKey: userPublicKey, + qortAmount: parseFloat(data.qortAmount), + fundingQortAmount: parseFloat(data.qortAmount) + 0.001, + foreignBlockchain: data.foreignBlockchain, + foreignAmount: parseFloat(data.foreignAmount), + tradeTimeout: 120, + receivingAddress: receivingAddress.address + }, keyPair) + + return response + } else { throw new Error("User declined request"); } } catch (error) { - throw new Error(error?.message || "Failed to submit trade order."); + throw new Error(error?.message || "Failed to submit sell order."); + } +}; + +export const cancelSellOrder = async (data, isFromExtension) => { + + const requiredFields = [ + "qortAmount", + "foreignBlockchain", + "foreignAmount", + "atAddress" + ]; + 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); + } + + try { + const fee = await getFee("MESSAGE"); + + const resPermission = await getUserPermission({ + text1: "Do you give this application permission to perform cancel a sell order?", + text2: `${data.qortAmount}${" "} + ${`QORT`}`, + text3: `FOR ${data.foreignAmount} ${data.foreignBlockchain}`, + fee: fee.fee + }, isFromExtension); + const { accepted } = resPermission; + if (accepted) { + const resKeyPair = await getKeyPair() + const parsedData = resKeyPair + const userPublicKey = parsedData.publicKey + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + const response = await cancelTradeOfferTradeBot({ + creatorPublicKey: userPublicKey, + atAddress: data.atAddress + }, keyPair) + + return response + + } else { + throw new Error("User declined request"); + } + } catch (error) { + throw new Error(error?.message || "Failed to submit sell order."); } }; \ No newline at end of file diff --git a/src/transactions/TradeBotCreateRequest.ts b/src/transactions/TradeBotCreateRequest.ts new file mode 100644 index 0000000..800ddec --- /dev/null +++ b/src/transactions/TradeBotCreateRequest.ts @@ -0,0 +1,65 @@ +// @ts-nocheck + +/** + * CrossChain - TradeBot Create Request (Sell Action) + * + * These are special types of transactions (JSON ENCODED) + */ + +export default class TradeBotCreateRequest { + constructor() { + // ... + } + + createTransaction(txnReq) { + this.creatorPublicKey(txnReq.creatorPublicKey) + this.qortAmount(txnReq.qortAmount) + this.fundingQortAmount(txnReq.fundingQortAmount) + this.foreignBlockchain(txnReq.foreignBlockchain) + this.foreignAmount(txnReq.foreignAmount) + this.tradeTimeout(txnReq.tradeTimeout) + this.receivingAddress(txnReq.receivingAddress) + + return this.txnRequest() + } + + creatorPublicKey(creatorPublicKey) { + this._creatorPublicKey = creatorPublicKey + } + + qortAmount(qortAmount) { + this._qortAmount = qortAmount + } + + fundingQortAmount(fundingQortAmount) { + this._fundingQortAmount = fundingQortAmount + } + + foreignBlockchain(foreignBlockchain) { + this._foreignBlockchain = foreignBlockchain + } + + foreignAmount(foreignAmount) { + this._foreignAmount = foreignAmount + } + + tradeTimeout(tradeTimeout) { + this._tradeTimeout = tradeTimeout + } + + receivingAddress(receivingAddress) { + this._receivingAddress = receivingAddress + } + + txnRequest() { + return { + creatorPublicKey: this._creatorPublicKey, + qortAmount: this._qortAmount, + fundingQortAmount: this._fundingQortAmount, + foreignBlockchain: this._foreignBlockchain, + foreignAmount: this._foreignAmount, + tradeTimeout: this._tradeTimeout, + receivingAddress: this._receivingAddress + } + } +} diff --git a/src/transactions/TradeBotDeleteRequest.ts b/src/transactions/TradeBotDeleteRequest.ts new file mode 100644 index 0000000..cd3c1b4 --- /dev/null +++ b/src/transactions/TradeBotDeleteRequest.ts @@ -0,0 +1,35 @@ +// @ts-nocheck + +/** + * CrossChain - DELETE TradeOffer + * + * These are special types of transactions (JSON ENCODED) + */ + +export default class DeleteTradeOffer { + constructor() { + // ... + } + + createTransaction(txnReq) { + this.creatorPublicKey(txnReq.creatorPublicKey) + this.atAddress(txnReq.atAddress) + + return this.txnRequest() + } + + creatorPublicKey(creatorPublicKey) { + this._creatorPublicKey = creatorPublicKey + } + + atAddress(atAddress) { + this._atAddress = atAddress + } + + txnRequest() { + return { + creatorPublicKey: this._creatorPublicKey, + atAddress: this._atAddress + } + } +} diff --git a/src/transactions/signTradeBotTransaction.ts b/src/transactions/signTradeBotTransaction.ts new file mode 100644 index 0000000..8f65f67 --- /dev/null +++ b/src/transactions/signTradeBotTransaction.ts @@ -0,0 +1,31 @@ +// @ts-nocheck + + +import nacl from '../deps/nacl-fast' +import Base58 from '../deps/Base58' +import utils from '../utils/utils' + +const signTradeBotTransaction = async (unsignedTxn, keyPair) => { + console.log('keypair', unsignedTxn, keyPair) + if (!unsignedTxn) { + throw new Error('Unsigned Transaction Bytes not defined') + } + + if (!keyPair) { + throw new Error('keyPair not defined') + } + + const txnBuffer = Base58.decode(unsignedTxn) + + if (keyPair.privateKey.length === undefined) { + const _privateKey = Object.keys(keyPair.privateKey).map(function (key) { return keyPair.privateKey[key] }) + const privateKey = new Uint8Array(_privateKey) + const signature = nacl.sign.detached(txnBuffer, privateKey) + return utils.appendBuffer(txnBuffer, signature) + } else { + const signature = nacl.sign.detached(txnBuffer, keyPair.privateKey) + return utils.appendBuffer(txnBuffer, signature) + } +} + +export default signTradeBotTransaction