diff --git a/src/atoms/global.ts b/src/atoms/global.ts index d546bf0..f05da75 100644 --- a/src/atoms/global.ts +++ b/src/atoms/global.ts @@ -42,7 +42,11 @@ export const sortablePinnedAppsAtom = atom({ { name: 'Q-Wallets', service: 'APP' - } + }, + { + name: 'Q-Search', + service: 'APP' + }, ], }); diff --git a/src/background.ts b/src/background.ts index 20dddd1..87a6416 100644 --- a/src/background.ts +++ b/src/background.ts @@ -2309,6 +2309,110 @@ export async function createGroup({ throw new Error(res?.message || "Transaction was not able to be processed"); return res; } +export async function sellName({ + name, + sellPrice +}) { + const wallet = await getSaveWallet(); + const address = wallet.address0; + if (!address) throw new Error("Cannot find user"); + const lastReference = await getLastRef(); + const feeres = await getFee("SELL_NAME"); + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + + const tx = await createTransaction(5, keyPair, { + fee: feeres.fee, + name, + sellPrice: sellPrice, + lastReference: lastReference, + }); + + const signedBytes = Base58.encode(tx.signedBytes); + + const res = await processTransactionVersion2(signedBytes); + if (!res?.signature) + throw new Error(res?.message || "Transaction was not able to be processed"); + return res; +} + +export async function cancelSellName({ + name +}) { + const wallet = await getSaveWallet(); + const address = wallet.address0; + if (!address) throw new Error("Cannot find user"); + const lastReference = await getLastRef(); + const feeres = await getFee("SELL_NAME"); + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + + const tx = await createTransaction(6, keyPair, { + fee: feeres.fee, + name, + lastReference: lastReference, + }); + + const signedBytes = Base58.encode(tx.signedBytes); + + const res = await processTransactionVersion2(signedBytes); + if (!res?.signature) + throw new Error(res?.message || "Transaction was not able to be processed"); + return res; +} + +export async function buyName({ + name, + sellerAddress, + sellPrice +}) { + const wallet = await getSaveWallet(); + const address = wallet.address0; + if (!address) throw new Error("Cannot find user"); + const lastReference = await getLastRef(); + const feeres = await getFee("BUY_NAME"); + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + console.log('tester', { + fee: feeres.fee, + name, + sellPrice, + recipient: sellerAddress, + lastReference: lastReference, + }) + const tx = await createTransaction(7, keyPair, { + fee: feeres.fee, + name, + sellPrice, + recipient: sellerAddress, + lastReference: lastReference, + }); + + const signedBytes = Base58.encode(tx.signedBytes); + + const res = await processTransactionVersion2(signedBytes); + if (!res?.signature) + throw new Error(res?.message || "Transaction was not able to be processed"); + return res; +} export async function updateGroup({ groupId, newOwner, diff --git a/src/components/Apps/AppsCategory.tsx b/src/components/Apps/AppsCategory.tsx index d1a1420..c1fa0a0 100644 --- a/src/components/Apps/AppsCategory.tsx +++ b/src/components/Apps/AppsCategory.tsx @@ -41,7 +41,8 @@ const officialAppList = [ "q-trade", "q-support", "q-manager", - "q-wallets" + "q-wallets", + "q-search" ]; const ScrollerStyled = styled('div')({ diff --git a/src/components/Apps/AppsCategoryDesktop.tsx b/src/components/Apps/AppsCategoryDesktop.tsx index f6ba48d..55cdc4f 100644 --- a/src/components/Apps/AppsCategoryDesktop.tsx +++ b/src/components/Apps/AppsCategoryDesktop.tsx @@ -49,7 +49,8 @@ const officialAppList = [ "q-trade", "q-support", "q-manager", - "q-wallets" + "q-wallets", + "q-search" ]; const ScrollerStyled = styled("div")({ diff --git a/src/components/Apps/AppsLibrary.tsx b/src/components/Apps/AppsLibrary.tsx index f9b58ea..5601626 100644 --- a/src/components/Apps/AppsLibrary.tsx +++ b/src/components/Apps/AppsLibrary.tsx @@ -43,7 +43,8 @@ const officialAppList = [ "q-trade", "q-support", "q-manager", - "q-wallets" + "q-wallets", + "q-search" ]; const ScrollerStyled = styled('div')({ diff --git a/src/components/Apps/AppsLibraryDesktop.tsx b/src/components/Apps/AppsLibraryDesktop.tsx index de356d4..851267b 100644 --- a/src/components/Apps/AppsLibraryDesktop.tsx +++ b/src/components/Apps/AppsLibraryDesktop.tsx @@ -59,7 +59,8 @@ const officialAppList = [ "q-support", "q-manager", "q-mintership", - "q-wallets" + "q-wallets", + "q-search" ]; const ScrollerStyled = styled("div")({ diff --git a/src/components/Apps/useQortalMessageListener.tsx b/src/components/Apps/useQortalMessageListener.tsx index 98af42d..bef9a99 100644 --- a/src/components/Apps/useQortalMessageListener.tsx +++ b/src/components/Apps/useQortalMessageListener.tsx @@ -256,7 +256,10 @@ export const listOfAllQortalRequests = [ 'GET_NODE_STATUS', 'GET_ARRR_SYNC_STATUS', 'SHOW_PDF_READER', - 'UPDATE_GROUP' + 'UPDATE_GROUP', + 'SELL_NAME', + 'CANCEL_SELL_NAME', + 'BUY_NAME' ] export const UIQortalRequests = [ @@ -313,7 +316,10 @@ export const UIQortalRequests = [ 'GET_NODE_STATUS', 'GET_ARRR_SYNC_STATUS', 'SHOW_PDF_READER', - 'UPDATE_GROUP' + 'UPDATE_GROUP', + 'SELL_NAME', + 'CANCEL_SELL_NAME', + 'BUY_NAME' ]; diff --git a/src/qortalRequests.ts b/src/qortalRequests.ts index 83c2186..f034dbd 100644 --- a/src/qortalRequests.ts +++ b/src/qortalRequests.ts @@ -1,6 +1,6 @@ -import { gateways, getApiKeyFromStorage } from "./background"; +import { gateways, getApiKeyFromStorage } from "./background"; import { listOfAllQortalRequests } from "./components/Apps/useQortalMessageListener"; -import { addForeignServer, addGroupAdminRequest, addListItems, adminAction, banFromGroupRequest, cancelGroupBanRequest, cancelGroupInviteRequest, cancelSellOrder, createAndCopyEmbedLink, createBuyOrder, createGroupRequest, createPoll, createSellOrder, decryptAESGCMRequest, decryptData, decryptDataWithSharingKey, decryptQortalGroupData, deleteHostedData, deleteListItems, deployAt, encryptData, encryptDataWithSharingKey, encryptQortalGroupData, getCrossChainServerInfo, getDaySummary, getNodeInfo, getNodeStatus, getForeignFee, getHostedData, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getUserWalletTransactions, getWalletBalance, inviteToGroupRequest, joinGroup, kickFromGroupRequest, leaveGroupRequest, openNewTab, publishMultipleQDNResources, publishQDNResource, registerNameRequest, removeForeignServer, removeGroupAdminRequest, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, signTransaction, updateForeignFee, updateNameRequest, voteOnPoll, getArrrSyncStatus, updateGroupRequest } from "./qortalRequests/get"; +import { addForeignServer, addGroupAdminRequest, addListItems, adminAction, banFromGroupRequest, cancelGroupBanRequest, cancelGroupInviteRequest, cancelSellOrder, createAndCopyEmbedLink, createBuyOrder, createGroupRequest, createPoll, createSellOrder, decryptAESGCMRequest, decryptData, decryptDataWithSharingKey, decryptQortalGroupData, deleteHostedData, deleteListItems, deployAt, encryptData, encryptDataWithSharingKey, encryptQortalGroupData, getCrossChainServerInfo, getDaySummary, getNodeInfo, getNodeStatus, getForeignFee, getHostedData, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getUserWalletTransactions, getWalletBalance, inviteToGroupRequest, joinGroup, kickFromGroupRequest, leaveGroupRequest, openNewTab, publishMultipleQDNResources, publishQDNResource, registerNameRequest, removeForeignServer, removeGroupAdminRequest, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, signTransaction, updateForeignFee, updateNameRequest, voteOnPoll, getArrrSyncStatus, updateGroupRequest, buyNameRequest, sellNameRequest, cancelSellNameRequest } from "./qortalRequests/get"; import { getData, storeData } from "./utils/chromeStorage"; import { executeEvent } from "./utils/events"; @@ -1257,6 +1257,63 @@ export const isRunningGateway = async ()=> { } break; } + case "BUY_NAME": { + try { + const res = await buyNameRequest(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 "SELL_NAME": { + try { + const res = await sellNameRequest(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 "CANCEL_SELL_NAME": { + try { + const res = await cancelSellNameRequest(request.payload, isFromExtension); + event.source.postMessage({ + requestId: request.requestId, + action: request.action, + payload: res, + type: "backgroundMessageResponse", + }, event.origin); + } catch (error) { + event.source.postMessage({ + requestId: request.requestId, + action: request.action, + error: error.message, + type: "backgroundMessageResponse", + }, event.origin); + } + break; + } default: break; } diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index 2b2607c..c0206b2 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -31,6 +31,10 @@ import { cancelInvitationToGroup, createGroup, updateGroup, + sellName, + cancelSellName, + buyName, + getBaseApi, } from "../background"; import { getNameInfo, uint8ArrayToObject } from "../backgroundFunctions/encryption"; import { showSaveFilePicker } from "../components/Apps/useQortalMessageListener"; @@ -4686,3 +4690,122 @@ export const decryptAESGCMRequest = async (data, isFromExtension) => { throw new Error("Failed to decrypt the message. Ensure the data and keys are correct."); } }; + + +export const sellNameRequest = async (data, isFromExtension) => { + const requiredFields = ["salePrice", "nameForSale"]; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (data[field] !== undefined && data[field] !== null) { + missingFields.push(field); + } + }); + const name = data.nameForSale + const sellPrice = +data.salePrice + + const validApi = await getBaseApi(); + + const response = await fetch(validApi + "/names/" + name); + const nameData = await response.json(); +if(!nameData) throw new Error("This name does not exist") + +if(nameData?.isForSale) throw new Error("This name is already for sale") + const fee = await getFee("SELL_NAME"); + const resPermission = await getUserPermission( + { + text1: `Do you give this application permission to create a sell name transaction?`, + highlightedText: `Sell ${name} for ${sellPrice} QORT`, + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await sellName({ + name, + sellPrice + }) + return response + + } else { + throw new Error("User declined request"); + } +}; + +export const cancelSellNameRequest = async (data, isFromExtension) => { + const requiredFields = ["nameForSale"]; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (data[field] !== undefined && data[field] !== null) { + missingFields.push(field); + } + }); + const name = data.nameForSale + const validApi = await getBaseApi(); + + const response = await fetch(validApi + "/names/" + name); + const nameData = await response.json(); +if(!nameData?.isForSale) throw new Error("This name is not for sale") + + const fee = await getFee("CANCEL_SELL_NAME"); + const resPermission = await getUserPermission( + { + text1: `Do you give this application permission to cancel the selling of a name?`, + highlightedText: `Name: ${name}`, + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await cancelSellName({ + name + }) + return response + + } else { + throw new Error("User declined request"); + } +}; + +export const buyNameRequest = async (data, isFromExtension) => { + const requiredFields = ["nameForSale"]; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (data[field] !== undefined && data[field] !== null) { + missingFields.push(field); + } + }); + const name = data.nameForSale + + const validApi = await getBaseApi(); + + const response = await fetch(validApi + "/names/" + name); + const nameData = await response.json(); + if(!nameData?.isForSale) throw new Error("This name is not for sale") + const sellerAddress = nameData.owner + const sellPrice = +nameData.salePrice + + + const fee = await getFee("BUY_NAME"); + const resPermission = await getUserPermission( + { + text1: `Do you give this application permission to buy a name?`, + highlightedText: `Buying ${name} for ${sellPrice} QORT`, + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await buyName({ + name, + sellerAddress, + sellPrice + }) + return response + + } else { + throw new Error("User declined request"); + } +}; \ No newline at end of file diff --git a/src/transactions/BuyNameTransacion.ts b/src/transactions/BuyNameTransacion.ts new file mode 100644 index 0000000..8c321d3 --- /dev/null +++ b/src/transactions/BuyNameTransacion.ts @@ -0,0 +1,45 @@ +// @ts-nocheck + +import { QORT_DECIMALS } from "../constants/constants" +import TransactionBase from "./TransactionBase" + + +export default class BuyNameTransacion extends TransactionBase { + constructor() { + super() + this.type = 7 + } + + set fee(fee) { + this._fee = fee * QORT_DECIMALS + this._feeBytes = this.constructor.utils.int64ToBytes(this._fee) + } + + set name(name) { + this.nameText = name + this._nameBytes = this.constructor.utils.stringtoUTF8Array(name) + this._nameLength = this.constructor.utils.int32ToBytes(this._nameBytes.length) + } + + set sellPrice(sellPrice) { + this._sellPrice = sellPrice * QORT_DECIMALS + this._sellPriceBytes = this.constructor.utils.int64ToBytes(this._sellPrice) + } + + set recipient(recipient) { + this._recipient = recipient instanceof Uint8Array ? recipient : this.constructor.Base58.decode(recipient) + this.theRecipient = recipient + } + + get params() { + const params = super.params + params.push( + this._nameLength, + this._nameBytes, + this._sellPriceBytes, + this._recipient, + this._feeBytes + ) + return params + } +} diff --git a/src/transactions/CancelSellNameTransacion.ts b/src/transactions/CancelSellNameTransacion.ts new file mode 100644 index 0000000..04296c8 --- /dev/null +++ b/src/transactions/CancelSellNameTransacion.ts @@ -0,0 +1,33 @@ +// @ts-nocheck + +import { QORT_DECIMALS } from "../constants/constants" +import TransactionBase from "./TransactionBase" + + +export default class CancelSellNameTransacion extends TransactionBase { + constructor() { + super() + this.type = 6 + } + + set fee(fee) { + this._fee = fee * QORT_DECIMALS + this._feeBytes = this.constructor.utils.int64ToBytes(this._fee) + } + + set name(name) { + this.nameText = name + this._nameBytes = this.constructor.utils.stringtoUTF8Array(name) + this._nameLength = this.constructor.utils.int32ToBytes(this._nameBytes.length) + } + + get params() { + const params = super.params + params.push( + this._nameLength, + this._nameBytes, + this._feeBytes + ) + return params + } +} diff --git a/src/transactions/SellNameTransacion.ts b/src/transactions/SellNameTransacion.ts new file mode 100644 index 0000000..3926568 --- /dev/null +++ b/src/transactions/SellNameTransacion.ts @@ -0,0 +1,40 @@ +// @ts-nocheck + +import { QORT_DECIMALS } from "../constants/constants" +import TransactionBase from "./TransactionBase" + + +export default class SellNameTransacion extends TransactionBase { + constructor() { + super() + this.type = 5 + } + + set fee(fee) { + this._fee = fee * QORT_DECIMALS + this._feeBytes = this.constructor.utils.int64ToBytes(this._fee) + } + + set name(name) { + this.nameText = name + this._nameBytes = this.constructor.utils.stringtoUTF8Array(name) + this._nameLength = this.constructor.utils.int32ToBytes(this._nameBytes.length) + } + + set sellPrice(sellPrice) { + this.showSellPrice = sellPrice + this._sellPrice = sellPrice * QORT_DECIMALS + this._sellPriceBytes = this.constructor.utils.int64ToBytes(this._sellPrice) + } + + get params() { + const params = super.params + params.push( + this._nameLength, + this._nameBytes, + this._sellPriceBytes, + this._feeBytes + ) + return params + } +} diff --git a/src/transactions/transactions.ts b/src/transactions/transactions.ts index 1f0accb..f989f3c 100644 --- a/src/transactions/transactions.ts +++ b/src/transactions/transactions.ts @@ -21,12 +21,18 @@ import RewardShareTransaction from './RewardShareTransaction.js' import RemoveRewardShareTransaction from './RemoveRewardShareTransaction.js' import UpdateNameTransaction from './UpdateNameTransaction.js' import UpdateGroupTransaction from './UpdateGroupTransaction.js' +import SellNameTransacion from './SellNameTransacion.js' +import CancelSellNameTransacion from './CancelSellNameTransacion.js' +import BuyNameTransacion from './BuyNameTransacion.js' export const transactionTypes = { + 2: PaymentTransaction, 3: RegisterNameTransaction, 4: UpdateNameTransaction, - 2: PaymentTransaction, + 5: SellNameTransacion, + 6: CancelSellNameTransacion, + 7: BuyNameTransacion, 8: CreatePollTransaction, 9: VoteOnPollTransaction, 16: DeployAtTransaction,