diff --git a/public/content-script.js b/public/content-script.js index 5b21b83..cc56687 100644 --- a/public/content-script.js +++ b/public/content-script.js @@ -729,7 +729,7 @@ async function storeFilesInIndexedDB(obj) { -const UIQortalRequests = ['GET_USER_ACCOUNT', 'ENCRYPT_DATA', 'DECRYPT_DATA', 'SEND_COIN', 'GET_LIST_ITEMS', 'ADD_LIST_ITEMS', 'DELETE_LIST_ITEM'] +const UIQortalRequests = ['GET_USER_ACCOUNT', 'ENCRYPT_DATA', 'DECRYPT_DATA', 'SEND_COIN', 'GET_LIST_ITEMS', 'ADD_LIST_ITEMS', 'DELETE_LIST_ITEM', 'VOTE_ON_POLL', 'CREATE_POLL'] if (!window.hasAddedQortalListener) { console.log("Listener added"); diff --git a/src/App.tsx b/src/App.tsx index 241e13e..0e818fc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1931,11 +1931,9 @@ function App() { > {messageQortalRequest?.text4} diff --git a/src/background.ts b/src/background.ts index e0058d0..d829ae3 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1059,7 +1059,7 @@ const processTransactionVersion2Chat = async (body: any, customApi) => { }); }; -const processTransactionVersion2 = async (body: any) => { +export const processTransactionVersion2 = async (body: any) => { const url = await createEndpoint(`/transactions/process?apiVersion=2`); try { @@ -1141,7 +1141,7 @@ const makeTransactionRequest = async ( return myTxnrequest; }; -const getLastRef = async () => { +export const getLastRef = async () => { const wallet = await getSaveWallet(); const address = wallet.address0; const validApi = await getBaseApi(); diff --git a/src/qortalRequests.ts b/src/qortalRequests.ts index 4e316db..4ac0dbc 100644 --- a/src/qortalRequests.ts +++ b/src/qortalRequests.ts @@ -1,4 +1,4 @@ -import { addListItems, decryptData, deleteListItems, encryptData, getListItems, getUserAccount, publishMultipleQDNResources, publishQDNResource, sendCoin } from "./qortalRequests/get"; +import { addListItems, createPoll, decryptData, deleteListItems, encryptData, getListItems, getUserAccount, publishMultipleQDNResources, publishQDNResource, sendCoin, voteOnPoll } from "./qortalRequests/get"; @@ -166,6 +166,32 @@ chrome?.runtime?.onMessage.addListener((request, sender, sendResponse) => { break; } + case "VOTE_ON_POLL": { + const data = request.payload; + + voteOnPoll(data) + .then((res) => { + sendResponse(res); + }) + .catch((error) => { + sendResponse({ error: error.message }); + }); + + break; + } + case "CREATE_POLL": { + const data = request.payload; + + createPoll(data) + .then((res) => { + sendResponse(res); + }) + .catch((error) => { + sendResponse({ error: error.message }); + }); + + break; + } case "SEND_COIN": { const data = request.payload; const requiredFields = ["coin", "destinationAddress", "amount"]; diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index acfbd64..ef00c53 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -2,7 +2,9 @@ import { createEndpoint, getFee, getKeyPair, + getLastRef, getSaveWallet, + processTransactionVersion2, removeDuplicateWindow, } from "../background"; import { getNameInfo } from "../backgroundFunctions/encryption"; @@ -17,8 +19,98 @@ import { } from "../qdn/encryption/group-encryption"; import { publishData } from "../qdn/publish/pubish"; import { getPermission, setPermission } from "../qortalRequests"; +import { createTransaction } from "../transactions/transactions"; import { fileToBase64 } from "../utils/fileReading"; + +const _createPoll = async (pollName, pollDescription, options) => { + + const fee = await getFee("CREATE_POLL"); + + const resPermission = await getUserPermission({ + text1: "You are requesting to create the poll below:", + text2: `Poll: ${pollName}`, + text3: `Description: ${pollDescription}`, + text4: `Options: ${options?.join(', ')}`, + fee: fee.fee, + }); + const { accepted } = resPermission; + + if(accepted){ + const wallet = await getSaveWallet(); + const address = wallet.address0; + 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, + }; + let lastRef = await getLastRef() + + const tx = await createTransaction(8, keyPair, { + fee: fee.fee, + ownerAddress: address, + rPollName: pollName, + rPollDesc: pollDescription, + rOptions: options, + lastReference: lastRef + }); + const signedBytes = Base58.encode(tx.signedBytes); + const res = await processTransactionVersion2(signedBytes); + if (!res?.signature) + throw new Error("Transaction was not able to be processed"); + return res; + } else { + throw new Error("User declined request"); + } + +} + +const _voteOnPoll =async (pollName, optionIndex, optionName)=> { + + const fee = await getFee("VOTE_ON_POLL"); + + const resPermission = await getUserPermission({ + text1: "You are being requested to vote on the poll below:", + text2: `Poll: ${pollName}`, + text3: `Option: ${optionName}`, + fee: fee.fee, + }); + const { accepted } = resPermission; + + if(accepted){ + const wallet = await getSaveWallet(); + const address = wallet.address0; + 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, + }; + let lastRef = await getLastRef() + + const tx = await createTransaction(9, keyPair, { + fee: fee.fee, + voterAddress: address, + rPollName: pollName, + rOptionIndex: optionIndex, + lastReference: lastRef + }); + const signedBytes = Base58.encode(tx.signedBytes); + const res = await processTransactionVersion2(signedBytes); + if (!res?.signature) + throw new Error("Transaction was not able to be processed"); + return res; + } else { + throw new Error("User declined request"); + } + +} + function getFileFromContentScript(fileId, sender) { console.log('sender', sender) return new Promise((resolve, reject) => { @@ -724,6 +816,71 @@ export const publishMultipleQDNResources = async (data: any, sender) => { return true; }; +export const voteOnPoll = async (data) => { + const requiredFields = ['pollName', 'optionIndex'] + const missingFields: string[] = [] + requiredFields.forEach((field) => { + if (!data[field] && data[field] !== 0) { + missingFields.push(field) + } + }) + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', ') + const errorMsg = `Missing fields: ${missingFieldsString}` + throw new Error(errorMsg) + } + const pollName = data.pollName + const optionIndex = data.optionIndex + let pollInfo = null + try { + const url = await createEndpoint(`/polls/${encodeURIComponent(pollName)}`); + const response = await fetch(url); + if (!response.ok) throw new Error("Failed to fetch poll"); + + pollInfo = await response.json(); + } catch (error) { + const errorMsg = (error && error.message) || 'Poll not found' + throw new Error(errorMsg) + } + if (!pollInfo || pollInfo.error) { + const errorMsg = (pollInfo && pollInfo.message) || 'Poll not found' + throw new Error(errorMsg) + } + try { + const optionName = pollInfo.pollOptions[optionIndex].optionName + const resVoteOnPoll = await _voteOnPoll(pollName, optionIndex, optionName) + return resVoteOnPoll + } catch (error) { + + throw new Error(error?.message || 'Failed to vote on the poll.') + } + }; + + export const createPoll = async (data) => { + const requiredFields = ['pollName', 'pollDescription', 'pollOptions', 'pollOwnerAddress'] + 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 pollName = data.pollName + const pollDescription = data.pollDescription + const pollOptions = data.pollOptions + const pollOwnerAddress = data.pollOwnerAddress + try { + const resCreatePoll = await _createPoll(pollName, pollDescription, pollOptions, pollOwnerAddress) + return resCreatePoll + } catch (error) { + throw new Error(error?.message || 'Failed to created poll.') + } + }; + export const sendCoin = async () => { try { const wallet = await getSaveWallet(); diff --git a/src/transactions/CreatePollTransaction.ts b/src/transactions/CreatePollTransaction.ts new file mode 100644 index 0000000..a2a9cc0 --- /dev/null +++ b/src/transactions/CreatePollTransaction.ts @@ -0,0 +1,73 @@ +// @ts-nocheck +import { QORT_DECIMALS } from '../constants/constants' +import TransactionBase from './TransactionBase' + +export default class CreatePollTransaction extends TransactionBase { + constructor() { + super() + this.type = 8 + this._options = [] + } + + addOption(option) { + const optionBytes = this.constructor.utils.stringtoUTF8Array(option) + const optionLength = this.constructor.utils.int32ToBytes(optionBytes.length) + this._options.push({ length: optionLength, bytes: optionBytes }) + } + + + set fee(fee) { + this._fee = fee * QORT_DECIMALS + this._feeBytes = this.constructor.utils.int64ToBytes(this._fee) + } + + set ownerAddress(ownerAddress) { + this._ownerAddress = ownerAddress instanceof Uint8Array ? ownerAddress : this.constructor.Base58.decode(ownerAddress) + } + + set rPollName(rPollName) { + this._rPollName = rPollName + this._rPollNameBytes = this.constructor.utils.stringtoUTF8Array(this._rPollName) + this._rPollNameLength = this.constructor.utils.int32ToBytes(this._rPollNameBytes.length) + + } + + set rPollDesc(rPollDesc) { + this._rPollDesc = rPollDesc + this._rPollDescBytes = this.constructor.utils.stringtoUTF8Array(this._rPollDesc) + this._rPollDescLength = this.constructor.utils.int32ToBytes(this._rPollDescBytes.length) + } + + set rOptions(rOptions) { + const optionsArray = rOptions[0].split(', ').map(opt => opt.trim()) + this._pollOptions = optionsArray + + for (let i = 0; i < optionsArray.length; i++) { + this.addOption(optionsArray[i]) + } + + this._rNumberOfOptionsBytes = this.constructor.utils.int32ToBytes(optionsArray.length) + } + + + get params() { + const params = super.params + params.push( + this._ownerAddress, + this._rPollNameLength, + this._rPollNameBytes, + this._rPollDescLength, + this._rPollDescBytes, + this._rNumberOfOptionsBytes + ) + + // Push the dynamic options + for (let i = 0; i < this._options.length; i++) { + params.push(this._options[i].length, this._options[i].bytes) + } + + params.push(this._feeBytes) + + return params + } +} diff --git a/src/transactions/VoteOnPollTransaction.ts b/src/transactions/VoteOnPollTransaction.ts new file mode 100644 index 0000000..327295e --- /dev/null +++ b/src/transactions/VoteOnPollTransaction.ts @@ -0,0 +1,38 @@ +// @ts-nocheck +import { QORT_DECIMALS } from '../constants/constants' +import TransactionBase from './TransactionBase' + +export default class VoteOnPollTransaction extends TransactionBase { + constructor() { + super() + this.type = 9 + } + + + set fee(fee) { + this._fee = fee * QORT_DECIMALS + this._feeBytes = this.constructor.utils.int64ToBytes(this._fee) + } + + set rPollName(rPollName) { + this._rPollName = rPollName + this._rPollNameBytes = this.constructor.utils.stringtoUTF8Array(this._rPollName) + this._rPollNameLength = this.constructor.utils.int32ToBytes(this._rPollNameBytes.length) + } + + set rOptionIndex(rOptionIndex) { + this._rOptionIndex = rOptionIndex + this._rOptionIndexBytes = this.constructor.utils.int32ToBytes(this._rOptionIndex) + } + + get params() { + const params = super.params + params.push( + this._rPollNameLength, + this._rPollNameBytes, + this._rOptionIndexBytes, + this._feeBytes + ) + return params + } +} diff --git a/src/transactions/transactions.ts b/src/transactions/transactions.ts index 25ddbf5..02854c6 100644 --- a/src/transactions/transactions.ts +++ b/src/transactions/transactions.ts @@ -14,11 +14,15 @@ import JoinGroupTransaction from './JoinGroupTransaction.js' import AddGroupAdminTransaction from './AddGroupAdminTransaction.js' import RemoveGroupAdminTransaction from './RemoveGroupAdminTransaction.js' import RegisterNameTransaction from './RegisterNameTransaction.js' +import VoteOnPollTransaction from './VoteOnPollTransaction.js' +import CreatePollTransaction from './CreatePollTransaction.js' export const transactionTypes = { 3: RegisterNameTransaction, 2: PaymentTransaction, + 8: CreatePollTransaction, + 9: VoteOnPollTransaction, 18: ChatTransaction, 181: GroupChatTransaction, 22: CreateGroupTransaction,