diff --git a/src/App.tsx b/src/App.tsx index 46f1474..6e08605 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -704,6 +704,9 @@ function App() { const qortalRequestPermissonFromExtension = async (message, event) => { if (message.action === "QORTAL_REQUEST_PERMISSION") { try { + if(message?.payload?.checkbox1){ + qortalRequestCheckbox1Ref.current = message?.payload?.checkbox1 + } await showQortalRequestExtension(message?.payload); if (qortalRequestCheckbox1Ref.current) { event.source.postMessage( @@ -1479,10 +1482,12 @@ function App() { textDecoration: "underline", }} onClick={async () => { - await Browser.open({ url: "https://www.qort.trade" }); + executeEvent("addTab", { data: { service: 'APP', name: 'q-trade' } }); + executeEvent("open-apps-mode", { }); + setIsOpenDrawerProfile(false); }} > - Get QORT at qort.trade + Get QORT at q-trade @@ -2832,6 +2837,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) => { @@ -1581,27 +1597,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: { @@ -1630,7 +1648,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 { @@ -1640,7 +1658,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 }, }; } @@ -1650,58 +1668,39 @@ 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({ qortAddress: proxyAccountAddress, recipientPublicKey: proxyAccountPublicKey, message, + atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress), }); + + if (res?.signature) { - let responseMessage; - - const message = await listenForChatMessageForBuyOrder({ - nodeBaseUrl: buyTradeNodeBaseUrl, - senderAddress: proxyAccountAddress, - senderPublicKey: proxyAccountPublicKey, - signature: res?.signature, - }); - // const status = response.callResponse === true ? 'trade-ongoing' : 'trade-failed' - // if (res?.encryptedMessageToBase58) { - // return { - // atAddresses: crosschainAtInfo.map((order) => order.qortalAtAddress), - // encryptedMessageToBase58: res?.encryptedMessageToBase58, - // node: buyTradeNodeBaseUrl, - // qortAddress: address, - // chatSignature: res?.signature, - // senderPublicKey: parsedData.publicKey, - // sender: address, - // reference: res?.reference, - // response.callResponse - // }; - // } - // return { - // atAddresses: crosschainAtInfo.map((order) => order.qortalAtAddress), - // chatSignature: res?.signature, - // node: buyTradeNodeBaseUrl, - // qortAddress: address, - // }; - responseMessage = { - callResponse: message.callResponse, - extra: { - message: message?.extra?.message, - senderAddress: address, - node: buyTradeNodeBaseUrl, - atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress), - }, - encryptedMessageToBase58 - } + + const message = await listenForChatMessageForBuyOrder({ + nodeBaseUrl: buyTradeNodeBaseUrl, + senderAddress: proxyAccountAddress, + senderPublicKey: proxyAccountPublicKey, + signature: res?.signature, + }); + const responseMessage = { + callResponse: message.callResponse, + extra: { + message: message?.extra?.message, + senderAddress: address, + node: buyTradeNodeBaseUrl, + atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress), + } + } + return responseMessage } else { throw new Error("Unable to send buy order message"); @@ -2306,14 +2305,7 @@ async function listenForChatMessageForBuyOrder({ senderPublicKey ); - // chrome.tabs.query({}, function (tabs) { - // tabs.forEach((tab) => { - // chrome.tabs.sendMessage(tab.id, { - // type: "RESPONSE_FOR_TRADES", - // message: parsedMessageObj, - // }); - // }); - // }); + return parsedMessageObj } catch (error) { console.error(error); throw new Error(error.message); diff --git a/src/components/Apps/Apps.tsx b/src/components/Apps/Apps.tsx index b444c22..6e013be 100644 --- a/src/components/Apps/Apps.tsx +++ b/src/components/Apps/Apps.tsx @@ -105,10 +105,19 @@ export const Apps = ({ mode, setMode, show , myName}) => { // dispatch(setIsLoadingGlobal(false)) } }, []); + useEffect(() => { + getCategories() + }, [getCategories]); + useEffect(() => { getQapps(); - getCategories() - }, [getQapps, getCategories]); + + const interval = setInterval(() => { + getQapps(); + }, 20 * 60 * 1000); // 20 minutes in milliseconds + + return () => clearInterval(interval); + }, [getQapps]); const selectedAppInfoFunc = (e) => { const data = e.detail?.data; @@ -292,6 +301,7 @@ export const Apps = ({ mode, setMode, show , myName}) => { myName={myName} hasPublishApp={!!(myApp || myWebsite)} categories={categories} + getQapps={getQapps} /> {mode === "appInfo" && !selectedTab && } diff --git a/src/components/Apps/AppsCategory.tsx b/src/components/Apps/AppsCategory.tsx index a999c95..8cd7fb8 100644 --- a/src/components/Apps/AppsCategory.tsx +++ b/src/components/Apps/AppsCategory.tsx @@ -39,6 +39,7 @@ const officialAppList = [ "qombo", "q-fund", "q-shop", + "q-trade" ]; const ScrollerStyled = styled('div')({ diff --git a/src/components/Apps/AppsLibrary.tsx b/src/components/Apps/AppsLibrary.tsx index cfe5b3f..838c501 100644 --- a/src/components/Apps/AppsLibrary.tsx +++ b/src/components/Apps/AppsLibrary.tsx @@ -26,6 +26,7 @@ import IconClearInput from "../../assets/svgs/ClearInput.svg"; import qappDevelopText from "../../assets/svgs/qappDevelopText.svg"; import qappDots from "../../assets/svgs/qappDots.svg"; import ReturnSVG from '../../assets/svgs/Return.svg' +import RefreshIcon from "@mui/icons-material/Refresh"; import { Spacer } from "../../common/Spacer"; import { AppInfoSnippet } from "./AppInfoSnippet"; @@ -41,6 +42,7 @@ const officialAppList = [ "qombo", "q-fund", "q-shop", + "q-trade" ]; const ScrollerStyled = styled('div')({ @@ -76,7 +78,7 @@ const ScrollerStyled = styled('div')({ "-ms-overflow-style": "none", }); -export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, isShow, categories={categories} }) => { +export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, isShow, categories, getQapps }) => { const [searchValue, setSearchValue] = useState(""); const virtuosoRef = useRef(); const { rootHeight } = useContext(MyContext); @@ -132,6 +134,11 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i justifyContent: "center", }} > + @@ -159,6 +166,21 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i )} + { + getQapps() + }} + > + + + diff --git a/src/components/Apps/useQortalMessageListener.tsx b/src/components/Apps/useQortalMessageListener.tsx index a5baa6a..451a65d 100644 --- a/src/components/Apps/useQortalMessageListener.tsx +++ b/src/components/Apps/useQortalMessageListener.tsx @@ -183,7 +183,8 @@ 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' + 'ADD_FOREIGN_SERVER', 'REMOVE_FOREIGN_SERVER', 'GET_DAY_SUMMARY', 'CREATE_TRADE_BUY_ORDER', + 'CREATE_TRADE_SELL_ORDER', 'CANCEL_TRADE_SELL_ORDER', 'IS_USING_GATEWAY' ]; @@ -434,7 +435,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/Chat/MessageDisplay.tsx b/src/components/Chat/MessageDisplay.tsx index 822f04b..12308b1 100644 --- a/src/components/Chat/MessageDisplay.tsx +++ b/src/components/Chat/MessageDisplay.tsx @@ -93,7 +93,7 @@ export const MessageDisplay = ({ htmlContent, isReply }) => { if (res) { const { service, name, identifier, path } = res; executeEvent("addTab", { data: { service, name, identifier, path } }); - executeEvent("open-dev-mode", { }); + executeEvent("open-apps-mode", { }); } } diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index 6a4f611..8780b62 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -1314,10 +1314,10 @@ export const Group = ({ }; useEffect(() => { - subscribeToEvent("open-dev-mode", openDevModeFunc); + subscribeToEvent("open-apps-mode", openDevModeFunc); return () => { - unsubscribeFromEvent("open-dev-mode", openDevModeFunc); + unsubscribeFromEvent("open-apps-mode", openDevModeFunc); }; }, []); diff --git a/src/components/Mobile/MobileFooter.tsx b/src/components/Mobile/MobileFooter.tsx index f8f1a86..7de9af1 100644 --- a/src/components/Mobile/MobileFooter.tsx +++ b/src/components/Mobile/MobileFooter.tsx @@ -17,6 +17,7 @@ import { WalletIcon } from "../../assets/Icons/WalletIcon"; import { HubsIcon } from "../../assets/Icons/HubsIcon"; import { TradingIcon } from "../../assets/Icons/TradingIcon"; import { MessagingIcon } from "../../assets/Icons/MessagingIcon"; +import { executeEvent } from "../../utils/events"; const IconWrapper = ({ children, label, color }) => { return ( @@ -209,7 +210,8 @@ export const MobileFooter = ({ /> { - await Browser.open({ url: 'https://www.qort.trade' }); + executeEvent("addTab", { data: { service: 'APP', name: 'q-trade' } }); + executeEvent("open-apps-mode", { }); }} icon={ diff --git a/src/qortalRequests.ts b/src/qortalRequests.ts index 0290da3..71e0d3c 100644 --- a/src/qortalRequests.ts +++ b/src/qortalRequests.ts @@ -1,4 +1,5 @@ -import { addForeignServer, addListItems, createBuyOrder, createPoll, decryptData, deleteListItems, deployAt, encryptData, getCrossChainServerInfo, getDaySummary, getForeignFee, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getWalletBalance, joinGroup, publishMultipleQDNResources, publishQDNResource, removeForeignServer, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, updateForeignFee, voteOnPoll } from "./qortalRequests/get"; +import { gateways, getApiKeyFromStorage } from "./background"; +import { addForeignServer, addListItems, cancelSellOrder, createBuyOrder, createPoll, decryptData, deleteListItems, deployAt, encryptData, getCrossChainServerInfo, getDaySummary, getForeignFee, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getWalletBalance, joinGroup, publishMultipleQDNResources, publishQDNResource, removeForeignServer, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, updateForeignFee, voteOnPoll } from "./qortalRequests/get"; import { getData, storeData } from "./utils/chromeStorage"; @@ -18,6 +19,16 @@ function setLocalStorage(key, data) { }); } +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) { try { @@ -604,6 +615,48 @@ 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 d38e7fc..a589273 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -28,10 +28,12 @@ import { uint8ArrayToBase64, } from "../qdn/encryption/group-encryption"; import { publishData } from "../qdn/publish/pubish"; -import { getPermission, setPermission } from "../qortalRequests"; +import { getPermission, setPermission, isRunningGateway } from "../qortalRequests"; import { createTransaction } from "../transactions/transactions"; import { mimeToExtensionMap } from "../utils/memeTypes"; - +import TradeBotCreateRequest from "../transactions/TradeBotCreateRequest"; +import DeleteTradeOffer from "../transactions/TradeBotDeleteRequest"; +import signTradeBotTransaction from "../transactions/signTradeBotTransaction"; const btcFeePerByte = 0.00000100 const ltcFeePerByte = 0.00000030 @@ -39,6 +41,13 @@ const dogeFeePerByte = 0.00001000 const dgbFeePerByte = 0.00000010 const rvnFeePerByte = 0.00001125 +const sellerForeignFee = { + 'LITECOIN': { + value: '~0.00005', + ticker: 'LTC' + } +} + function roundUpToDecimals(number, decimals = 8) { const factor = Math.pow(10, decimals); // Create a factor based on the number of decimals return Math.ceil(+number * factor) / factor; @@ -1359,22 +1368,30 @@ export const getWalletBalance = async (data, bypassPermission?: boolean, isFromE const errorMsg = `Missing fields: ${missingFieldsString}`; throw new Error(errorMsg); } + + const value = (await getPermission(`qAPPAutoWalletBalance-${data.coin}`)) || false; + console.log('value', value) + let skip = false; + if (value) { + skip = true; + } let resPermission - if(!bypassPermission){ + if(!bypassPermission && !skip){ resPermission = await getUserPermission({ text1: "Do you give this application permission to fetch your", highlightedText: `${data.coin} balance`, + checkbox1: { + value: true, + label: "Always allow balance to be retrieved automatically", + }, }, isFromExtension); - } else { - resPermission = { - accepted: false - } + } + const { accepted = false, checkbox1 = false } = resPermission || {}; + if(resPermission){ + setPermission(`qAPPAutoWalletBalance-${data.coin}`, checkbox1); } - - const { accepted } = resPermission; - - if (accepted || bypassPermission) { + if (accepted || bypassPermission || skip) { let coin = data.coin; const wallet = await getSaveWallet(); const address = wallet.address0; @@ -1461,7 +1478,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; @@ -2394,11 +2411,13 @@ export const sendCoin = async (data, isFromExtension) => { }; + + export const createBuyOrder = async (data, isFromExtension) => { const requiredFields = [ "crosschainAtInfo", - "processType" + "foreignBlockchain" ]; const missingFields: string[] = []; requiredFields.forEach((field) => { @@ -2411,12 +2430,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({ @@ -2433,16 +2450,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 { @@ -2451,4 +2470,249 @@ export const createBuyOrder = async (data, isFromExtension) => { } catch (error) { throw new Error(error?.message || "Failed to submit trade order."); } +}; + + 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 = [ + "qortAmount", + "foreignBlockchain", + "foreignAmount" + ]; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(", "); + const errorMsg = `Missing fields: ${missingFieldsString}`; + throw new Error(errorMsg); + } + +const receivingAddress = await getUserWalletFunc('LTC') + try { + const resPermission = await getUserPermission({ + 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 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 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..929898f --- /dev/null +++ b/src/transactions/signTradeBotTransaction.ts @@ -0,0 +1,30 @@ +// @ts-nocheck + + +import nacl from '../deps/nacl-fast' +import Base58 from '../deps/Base58' +import utils from '../utils/utils' + +const signTradeBotTransaction = async (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