diff --git a/core/language/us.json b/core/language/us.json
index 76458f9f..fa6d0460 100644
--- a/core/language/us.json
+++ b/core/language/us.json
@@ -1024,7 +1024,13 @@
"rewarddialog6": "On pressing confirm, the rewardshare will be removed and the minting key will become invalid.",
"deployAtdialog1": "You are deploying the AT",
"deployAtdialog2": "On pressing confirm, the AT will be deployed!",
- "deployAtdialog3": "Initial amount balance"
+ "deployAtdialog3": "Initial amount balance",
+ "votedialog1": "You are requesting to vote on the poll below:",
+ "votedialog2": "On pressing confirm, the vote request will be sent!",
+ "votedialog3": "You are requesting to create the poll below:",
+ "votedialog4": "Poll Description",
+ "votedialog5": "Options",
+ "votedialog6": "On pressing confirm, the poll will be created!"
},
"sponsorshipspage": {
"schange1": "Active Sponsorships",
diff --git a/crypto/api/transactions/polls/CreatePollTransaction.js b/crypto/api/transactions/polls/CreatePollTransaction.js
new file mode 100644
index 00000000..b7d8bb6c
--- /dev/null
+++ b/crypto/api/transactions/polls/CreatePollTransaction.js
@@ -0,0 +1,111 @@
+'use strict'
+import TransactionBase from '../TransactionBase.js'
+import { QORT_DECIMALS } from '../../constants.js'
+
+export default class CreatePollTransaction extends TransactionBase {
+ constructor() {
+ super()
+ this.type = 8
+ this._options = []
+ }
+
+ render(html) {
+ return html`
+ ${this._votedialog3}
+
+ ${this._rPollName}
+
+ ${this._votedialog4}
+
+ ${this._rPollDesc}
+
+ ${this._votedialog5}
+
+ ${this._pollOptions.join(', ')}
+
+ ${this._votedialog6}
+
+ ${this._feeDialog}: ${this._feeDisplay}
+
+ `
+ }
+
+ 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 feeDialog(feeDialog){
+ this._feeDialog = feeDialog
+ }
+
+ set votedialog3(votedialog3) {
+ this._votedialog3 = votedialog3
+ }
+
+ set votedialog4(votedialog4) {
+ this._votedialog4 = votedialog4
+ }
+ set votedialog5(votedialog5) {
+ this._votedialog5 = votedialog5
+ }
+ set votedialog6(votedialog6) {
+ this._votedialog6 = votedialog6
+ }
+
+ set fee(fee) {
+ this._feeDisplay = 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.toLocaleLowerCase())
+ 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
+ console.log('this._options', this._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
+ }
+}
\ No newline at end of file
diff --git a/crypto/api/transactions/polls/VoteOnPollTransaction.js b/crypto/api/transactions/polls/VoteOnPollTransaction.js
new file mode 100644
index 00000000..9fbba6ff
--- /dev/null
+++ b/crypto/api/transactions/polls/VoteOnPollTransaction.js
@@ -0,0 +1,65 @@
+'use strict'
+import TransactionBase from '../TransactionBase.js'
+import { QORT_DECIMALS } from '../../constants.js'
+
+export default class VoteOnPollTransaction extends TransactionBase {
+ constructor() {
+ super()
+ this.type = 9
+ }
+
+ render(html) {
+ return html`
+ ${this._votedialog1}
+
+ ${this._rPollName}
+
+ ${this._votedialog2}
+
+ ${this._feeDialog}: ${this._feeDisplay}
+
+ `
+ }
+
+ set feeDialog(feeDialog){
+ this._feeDialog = feeDialog
+ }
+
+ set votedialog1(votedialog1) {
+ this._votedialog1 = votedialog1
+ }
+
+ set votedialog2(votedialog2) {
+ this._votedialog2 = votedialog2
+ }
+
+ set fee(fee) {
+ this._feeDisplay = 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
+ }
+}
\ No newline at end of file
diff --git a/crypto/api/transactions/transactions.js b/crypto/api/transactions/transactions.js
index 0423d3ae..4f0e92fd 100644
--- a/crypto/api/transactions/transactions.js
+++ b/crypto/api/transactions/transactions.js
@@ -23,6 +23,8 @@ import RewardShareTransaction from './reward-share/RewardShareTransaction.js'
import RemoveRewardShareTransaction from './reward-share/RemoveRewardShareTransaction.js'
import TransferPrivsTransaction from './TransferPrivsTransaction.js'
import DeployAtTransaction from './DeployAtTransaction.js'
+import VoteOnPollTransaction from './polls/VoteOnPollTransaction.js'
+import CreatePollTransaction from './polls/CreatePollTransaction.js'
export const transactionTypes = {
2: PaymentTransaction,
@@ -31,6 +33,8 @@ export const transactionTypes = {
5: SellNameTransacion,
6: CancelSellNameTransacion,
7: BuyNameTransacion,
+ 8: CreatePollTransaction,
+ 9: VoteOnPollTransaction,
16: DeployAtTransaction,
17: MessageTransaction,
18: ChatTransaction,
diff --git a/plugins/plugins/core/components/qdn-action-types.js b/plugins/plugins/core/components/qdn-action-types.js
index 59385690..496aba42 100644
--- a/plugins/plugins/core/components/qdn-action-types.js
+++ b/plugins/plugins/core/components/qdn-action-types.js
@@ -59,4 +59,10 @@ export const OPEN_NEW_TAB = 'OPEN_NEW_TAB'
export const NOTIFICATIONS_PERMISSION = 'NOTIFICATIONS_PERMISSION'
//SEND_LOCAL_NOTIFICATION
-export const SEND_LOCAL_NOTIFICATION = 'SEND_LOCAL_NOTIFICATION'
\ No newline at end of file
+export const SEND_LOCAL_NOTIFICATION = 'SEND_LOCAL_NOTIFICATION'
+
+//VOTE_ON_POLL
+export const VOTE_ON_POLL= 'VOTE_ON_POLL'
+
+//CREATE_POLL
+export const CREATE_POLL= 'CREATE_POLL'
\ No newline at end of file
diff --git a/plugins/plugins/core/qdn/browser/browser.src.js b/plugins/plugins/core/qdn/browser/browser.src.js
index 0b8086dd..0d411ab7 100644
--- a/plugins/plugins/core/qdn/browser/browser.src.js
+++ b/plugins/plugins/core/qdn/browser/browser.src.js
@@ -496,6 +496,33 @@ class WebBrowser extends LitElement {
return qortFee
}
+ async unitVoteFee() {
+ const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
+ const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
+ const url = `${nodeUrl}/transactions/unitfee?txType=VOTE_ON_POLL`
+ const response = await fetch(url)
+ if (!response.ok) {
+ throw new Error('Error when fetching vote fee');
+ }
+
+ const data = await response.json()
+ const joinFee = (Number(data) / 1e8).toFixed(8)
+ return joinFee
+ }
+ async unitCreatePollFee() {
+ const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
+ const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
+ const url = `${nodeUrl}/transactions/unitfee?txType=CREATE_POLL`
+ const response = await fetch(url)
+ if (!response.ok) {
+ throw new Error('Error when fetching vote fee');
+ }
+
+ const data = await response.json()
+ const joinFee = (Number(data) / 1e8).toFixed(8)
+ return joinFee
+ }
+
async _joinGroup(groupId, groupName) {
const joinFeeInput = await this.unitJoinFee()
const getLastRef = async () => {
@@ -610,6 +637,122 @@ class WebBrowser extends LitElement {
}
+ async _voteOnPoll(pollName, optionIndex) {
+ const voteFeeInput = await this.unitVoteFee()
+ const getLastRef = async () => {
+ let myRef = await parentEpml.request('apiCall', {
+ type: 'api',
+ url: `/addresses/lastreference/${this.selectedAddress.address}`
+ })
+ return myRef
+ };
+
+ const validateReceiver = async () => {
+ let lastRef = await getLastRef();
+ let myTransaction = await makeTransactionRequest(lastRef)
+ const res = getTxnRequestResponse(myTransaction)
+ return res
+ }
+
+ const makeTransactionRequest = async (lastRef) => {
+ let votedialog1 = get("transactions.votedialog1")
+ let votedialog2 = get("transactions.votedialog2")
+ let feeDialog = get("walletpage.wchange12")
+
+ let myTxnrequest = await parentEpml.request('transaction', {
+ type: 9,
+ nonce: this.selectedAddress.nonce,
+ params: {
+ fee: voteFeeInput,
+ voterAddress: this.selectedAddress.address,
+ rPollName: pollName,
+ rOptionIndex: optionIndex,
+ lastReference: lastRef,
+ votedialog1: votedialog1,
+ votedialog2: votedialog2,
+ feeDialog
+ },
+ apiVersion: 2
+ })
+ return myTxnrequest
+ }
+
+ const getTxnRequestResponse = (txnResponse) => {
+ if (txnResponse.success === false && txnResponse.message) {
+ throw new Error(txnResponse.message)
+ } else if (txnResponse.success === true && !txnResponse.data.error) {
+ return txnResponse.data
+ } else if (txnResponse.data && txnResponse.data.message) {
+ throw new Error(txnResponse.data.message)
+ } else {
+ throw new Error('Server error. Could not perform action.')
+ }
+ }
+ const voteRes = await validateReceiver()
+ return voteRes
+
+ }
+ async _createPoll(pollName, pollDescription, options, pollOwnerAddress) {
+ const voteFeeInput = await this.unitCreatePollFee()
+ const getLastRef = async () => {
+ let myRef = await parentEpml.request('apiCall', {
+ type: 'api',
+ url: `/addresses/lastreference/${this.selectedAddress.address}`
+ })
+ return myRef
+ };
+
+ const validateReceiver = async () => {
+ let lastRef = await getLastRef();
+ let myTransaction = await makeTransactionRequest(lastRef)
+ const res = getTxnRequestResponse(myTransaction)
+ return res
+ }
+
+ const makeTransactionRequest = async (lastRef) => {
+ let votedialog3 = get("transactions.votedialog3")
+ let votedialog4 = get("transactions.votedialog4")
+ let votedialog5 = get("transactions.votedialog5")
+ let votedialog6 = get("transactions.votedialog6")
+ let feeDialog = get("walletpage.wchange12")
+
+ let myTxnrequest = await parentEpml.request('transaction', {
+ type: 8,
+ nonce: this.selectedAddress.nonce,
+ params: {
+ fee: voteFeeInput,
+ ownerAddress: pollOwnerAddress,
+ rPollName: pollName,
+ rPollDesc: pollDescription,
+ rOptions: options,
+ lastReference: lastRef,
+ votedialog3: votedialog3,
+ votedialog4: votedialog4,
+ votedialog5: votedialog5,
+ votedialog6: votedialog6,
+ feeDialog
+ },
+ apiVersion: 2
+ })
+ return myTxnrequest
+ }
+
+ const getTxnRequestResponse = (txnResponse) => {
+ if (txnResponse.success === false && txnResponse.message) {
+ throw new Error(txnResponse.message)
+ } else if (txnResponse.success === true && !txnResponse.data.error) {
+ return txnResponse.data
+ } else if (txnResponse.data && txnResponse.data.message) {
+ throw new Error(txnResponse.data.message)
+ } else {
+ throw new Error('Server error. Could not perform action.')
+ }
+ }
+ const voteRes = await validateReceiver()
+ return voteRes
+
+ }
+
firstUpdated() {
this.changeTheme();
this.changeLanguage();
@@ -1324,6 +1467,103 @@ class WebBrowser extends LitElement {
// If they decline, send back JSON that includes an `error` key, such as `{"error": "User declined request"}`
break;
}
+ case actions.VOTE_ON_POLL: {
+ const requiredFields = ['pollName', 'optionIndex'];
+ const missingFields = [];
+
+ requiredFields.forEach((field) => {
+ if (!data[field]) {
+ missingFields.push(field);
+ }
+ });
+
+ if (missingFields.length > 0) {
+ const missingFieldsString = missingFields.join(', ');
+ const errorMsg = `Missing fields: ${missingFieldsString}`
+ let data = {};
+ data['error'] = errorMsg;
+ response = JSON.stringify(data);
+ break
+ }
+ const pollName = data.pollName;
+ const optionIndex = data.optionIndex;
+
+
+ let pollInfo = null
+ try {
+ pollInfo = await parentEpml.request("apiCall", {
+ type: "api",
+ url: `/polls/${pollName}`,
+ });
+ } catch (error) {
+ const errorMsg = (error && error.message) || 'Poll not found';
+ let obj = {};
+ obj['error'] = errorMsg;
+ response = JSON.stringify(obj);
+ break
+ }
+
+ if (!pollInfo || pollInfo.error) {
+ const errorMsg = (pollInfo && pollInfo.message) || 'Poll not found';
+ let obj = {};
+ obj['error'] = errorMsg;
+ response = JSON.stringify(obj);
+ break
+ }
+
+ try {
+ this.loader.show();
+ const resVoteOnPoll = await this._voteOnPoll(pollName, optionIndex)
+ response = JSON.stringify(resVoteOnPoll);
+ } catch (error) {
+ const obj = {};
+ const errorMsg = error.message || 'Failed to vote on the poll.';
+ obj['error'] = errorMsg;
+ response = JSON.stringify(obj);
+ } finally {
+ this.loader.hide();
+ }
+
+ break;
+ }
+ case actions.CREATE_POLL: {
+ const requiredFields = ['pollName', 'pollDescription', 'pollOptions', 'pollOwnerAddress'];
+ const missingFields = [];
+
+ requiredFields.forEach((field) => {
+ if (!data[field]) {
+ missingFields.push(field);
+ }
+ });
+
+ if (missingFields.length > 0) {
+ const missingFieldsString = missingFields.join(', ');
+ const errorMsg = `Missing fields: ${missingFieldsString}`
+ let data = {};
+ data['error'] = errorMsg;
+ response = JSON.stringify(data);
+ break
+ }
+ const pollName = data.pollName;
+ const pollDescription = data.pollDescription
+ const pollOptions = data.pollOptions
+ const pollOwnerAddress = data.pollOwnerAddress
+
+ try {
+ this.loader.show();
+ const resCreatePoll = await this._createPoll(pollName, pollDescription, pollOptions, pollOwnerAddress)
+ response = JSON.stringify(resCreatePoll);
+ } catch (error) {
+ const obj = {};
+ const errorMsg = error.message || 'Failed to created poll.';
+ obj['error'] = errorMsg;
+ response = JSON.stringify(obj);
+ } finally {
+ this.loader.hide();
+ }
+
+ break;
+ }
case actions.OPEN_NEW_TAB: {
if(!data.qortalLink){
const obj = {};