nem-voting
Version:
351 lines • 15.8 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("./utils");
const nem_library_1 = require("nem-library");
const constants_1 = require("./constants");
const counting_1 = require("./counting");
const rxjs_1 = require("rxjs");
const voting_1 = require("./voting");
const poll_index_1 = require("./poll-index");
/**
* Abstract class that represents a poll
*/
class Poll {
/**
* @internal
* @param formData
* @param description
* @param options
* @param whitelist
*/
constructor(formData, description, options, whitelist) {
this.data = {
formData: (formData),
description: (description),
options: (options),
};
if (whitelist) {
this.data.whitelist = whitelist;
}
}
}
exports.Poll = Poll;
/**
* An unbroadcasted poll. Exists only locally and not on the blockchain yet
*/
class UnbroadcastedPoll extends Poll {
constructor(formData, description, options, whitelist) {
super(formData, description, options, whitelist);
/**
* Broadcasts an unbroadcasted poll and returns the resulting broadcasted poll object (as a promise)
* @param creatorPublicKey - public key of the poll creator
* @param pollIndex - optionally provide the poll index to send the poll to.
* If not specified the default public index is used
* @return {pollAddress: Address, transactions: TransferTransaction[]} - returns the poll address
* and the transactions that need to be sent for it to be broadcasted
*/
this.broadcast = (creatorPublicKey, pollIndex) => {
try {
const pollAddress = utils_1.generatePollAddress(this.data.formData.title, creatorPublicKey);
const link = {};
const simplifiedLink = {};
this.data.options.forEach((option) => {
const addr = utils_1.deriveOptionAddress(pollAddress, option);
link[option] = addr;
simplifiedLink[option] = addr.plain();
});
const formDataMessage = "formData:" + JSON.stringify(this.data.formData);
const descriptionMessage = "description:" + this.data.description;
const optionsObject = { strings: this.data.options, link: (simplifiedLink) };
const optionsMessage = "options:" + JSON.stringify(optionsObject);
const formData = utils_1.getMessageTransaction(formDataMessage, pollAddress);
const description = utils_1.getMessageTransaction(descriptionMessage, pollAddress);
const options = utils_1.getMessageTransaction(optionsMessage, pollAddress);
let messages = [formData, description, options];
if (this.data.formData.type === constants_1.PollConstants.WHITELIST_POLL) {
const splitAddresses = [];
const addresses = this.data.whitelist.map((a) => a.plain());
while (addresses.length > 0) {
splitAddresses.push(addresses.splice(0, 23)); // 23 is the maximum amount of addresses that fit in a single transaction
}
const whitelistMessages = splitAddresses.map((partialWhitelist) => {
const whitelistMessage = "whitelist:" + JSON.stringify(partialWhitelist);
return utils_1.getMessageTransaction(whitelistMessage, pollAddress);
});
messages = messages.concat(whitelistMessages);
}
const header = {
title: this.data.formData.title,
type: this.data.formData.type,
doe: this.data.formData.doe,
address: pollAddress.plain(),
};
const headerMessage = "poll:" + JSON.stringify(header);
let pollIndexAddress;
if (pollIndex) {
pollIndexAddress = pollIndex.address;
}
else {
pollIndexAddress = (nem_library_1.NEMLibrary.getNetworkType() === nem_library_1.NetworkTypes.MAIN_NET) ?
new nem_library_1.Address(constants_1.PollConstants.MAINNET_POLL_INDEX) : new nem_library_1.Address(constants_1.PollConstants.TESTNET_POLL_INDEX);
}
messages.push(utils_1.getMessageTransaction(headerMessage, pollIndexAddress));
return {
transactions: messages,
broadcastedPoll: new BroadcastedPoll(this.data.formData, this.data.description, this.data.options, pollAddress, link, this.data.whitelist),
};
}
catch (err) {
throw err;
}
};
this.getBroadcastFee = () => {
const pubKey = utils_1.generateRandomPubKey();
const addr = utils_1.generateRandomAddress();
const pollIndex = new poll_index_1.PollIndex(addr, false, []);
const broadcastData = this.broadcast(pubKey, pollIndex);
const total = broadcastData.transactions.reduce((acc, t) => {
return acc + t.fee;
}, 0);
return total;
};
}
}
exports.UnbroadcastedPoll = UnbroadcastedPoll;
/**
* A broadcasted poll. Represents a Poll that exists in the blockchain.
*/
class BroadcastedPoll extends Poll {
/**
* @internal
*/
constructor(formData, description, options, pollAddress, optionAddresses, whitelist, endBlock) {
super(formData, description, options, whitelist);
/**
* Gets the option address for a given option
* @param option - The option
* @return Address | null
*/
this.getOptionAddress = (option) => {
const address = this.optionAddresses[option];
if (!address) {
return null;
}
else {
return address;
}
};
/**
* Sets the block when the poll ends
* @param block - The end block
* @return void
*/
this.setEndBlock = (block) => {
this.endBlock = block;
};
/**
* Get the transactions needed to be broadcasted by the creator of the poll for extending the whitelist before the poll ending
* @param addresses - The additional addresses to be added
*/
this.extendWhitelist = (addresses) => {
if (this.data.formData.type !== constants_1.PollConstants.WHITELIST_POLL) {
throw new Error("This poll is not a whitelist poll");
}
const splitAddresses = [];
const plainAddresses = addresses.map((a) => a.plain());
while (plainAddresses.length > 0) {
splitAddresses.push(plainAddresses.splice(0, 23));
}
const whitelistMessages = splitAddresses.map((partialWhitelist) => {
const whitelistMessage = "whitelist:" + JSON.stringify(partialWhitelist);
return utils_1.getMessageTransaction(whitelistMessage, this.address);
});
return whitelistMessages;
};
/**
* Gets the results for the poll
* @param pollAddress - The poll's NEM Address
* @return Observable<IResults>
*/
this.getResults = () => {
const poll = this;
if (poll.data.formData.type === constants_1.PollConstants.POI_POLL) {
return counting_1.getPOIResults(poll);
}
else if (poll.data.formData.type === constants_1.PollConstants.WHITELIST_POLL) {
return counting_1.getWhitelistResults(poll);
}
else {
throw new Error("unsupported type");
}
};
/**
* Gets the results for the poll as a csv string
* @param pollAddress - The poll's NEM Address
* @return Observable<string>
*/
this.getCsvResults = () => {
const poll = this;
if (poll.data.formData.type === constants_1.PollConstants.POI_POLL) {
return counting_1.getPOIResultsCsv(poll);
}
else {
throw new Error("CSV results only available for POI polls");
}
};
/**
* Gets the results for the poll as an array of vote objects
* @param pollAddress - The poll's NEM Address
* @return Observable<IResults>
*/
this.getVoters = () => {
const poll = this;
if (poll.data.formData.type === constants_1.PollConstants.POI_POLL) {
return counting_1.getPOIResultsArray(poll);
}
else {
throw new Error("voters function only available for POI polls");
}
};
/**
* validates a poll's structure and returns wether it is correct or not
* @return boolean
*/
this.validate = () => {
const sortedOptions = this.data.options.sort();
if (sortedOptions.some((v, i, a) => (i !== 0 && a[i - 1] === v))) {
return false;
}
const pollAddress = this.address;
const validOptionAddress = (option) => {
const expected = utils_1.deriveOptionAddress(pollAddress, option);
const actual = this.getOptionAddress(option);
if (!actual) {
return false;
}
return (expected.plain() === actual.plain());
};
return (sortedOptions.every(validOptionAddress));
};
/**
* Votes on the poll from a given account, returns the vote transaction result
* @param option - The option to vote
* @return TransferTransaction - the transaction that needs to be sent to vote
*/
this.vote = (option) => {
const now = Date.now();
if (this.data.formData.doe < now) {
throw new Error("Poll Ended");
}
return voting_1.vote(this, option);
};
/**
* Votes on the poll from a multisig account, returns the vote transaction result
* @param multisigAccount - The public account of the multisig account that votes
* @param option - The option to vote
* @return MultisigTransaction - the transaction that needs to be sent to vote
*/
this.voteMultisig = (multisigAccount, option) => {
const now = Date.now();
if (this.data.formData.doe < now) {
throw new Error("Poll Ended");
}
return voting_1.multisigVote(multisigAccount, this, option);
};
/**
* Gets the votes that an address has sent to the poll, if it has not voted returns null
* @param address - The address of the voter
* @return Observable<Transaction[] | null>
*/
this.getVotes = (address) => {
return voting_1.getVotes(address, this);
};
this.address = pollAddress;
this.optionAddresses = optionAddresses;
this.endBlock = endBlock;
}
}
/**
* Fetches a Broadcasted Poll from the blockchain by its address
* @param pollAddress - The poll's NEM Address
* @return Promise<BroadcastedPoll>
*/
BroadcastedPoll.fromAddressPromise = (pollAddress) => __awaiter(this, void 0, void 0, function* () {
try {
const formDataPromise = utils_1.getFirstMessageWithString("formData:", pollAddress).first().toPromise();
const descriptionPromise = utils_1.getFirstMessageWithString("description:", pollAddress).first().toPromise();
const optionsPromise = utils_1.getFirstMessageWithString("options:", pollAddress).first().toPromise();
const pollBasicData = yield Promise.all([formDataPromise, descriptionPromise, optionsPromise]);
if (pollBasicData.some((e) => e === null)) {
throw new Error("Error fetching poll");
}
const formData = JSON.parse(pollBasicData[0].replace("formData:", ""));
const description = pollBasicData[1].replace("description:", "");
const options = JSON.parse(pollBasicData[2].replace("options:", ""));
const unique = (list) => {
return list.sort().filter((item, pos, ary) => {
return !pos || item !== ary[pos - 1];
});
};
// This part is for compatibility with the old poll structure
const addressLink = {};
if (options.link) {
options.strings.forEach((option) => {
addressLink[option] = new nem_library_1.Address(options.link[option]);
});
}
else {
options.addresses = options.addresses.sort();
options.strings.forEach((option, i) => {
addressLink[option] = new nem_library_1.Address(options.addresses[i]);
});
}
const orderedAddresses = Object.keys(addressLink).map((option) => addressLink[option]);
if (orderedAddresses.length !== unique(orderedAddresses).length || Object.keys(addressLink).length !== options.strings.length) {
// same account for different options
throw Error("Poll is invalid");
}
if (formData.type === constants_1.PollConstants.WHITELIST_POLL) {
let endBlock;
const creator = yield utils_1.getFirstSender(pollAddress).first().toPromise();
const end = (formData.doe < Date.now()) ? (formData.doe) : (undefined);
if (end !== undefined) {
endBlock = yield utils_1.getHeightByTimestamp(end).first().toPromise();
}
const whitelistStrings = yield utils_1.getAllMessagesWithString("whitelist:", pollAddress, creator, endBlock).first().toPromise();
if (whitelistStrings === null) {
throw new Error("Error fetching poll");
}
const whitelist = whitelistStrings.reduce((addresses, whitelistString) => {
return addresses.concat(JSON.parse(whitelistString.replace("whitelist:", "")).map((a) => {
return new nem_library_1.Address(a);
}));
}, []);
return new BroadcastedPoll(formData, description, options.strings, pollAddress, addressLink, whitelist, endBlock);
}
else {
return new BroadcastedPoll(formData, description, options.strings, pollAddress, addressLink);
}
}
catch (err) {
throw err;
}
});
/**
* Fetches a Broadcasted Poll from the blockchain by its address
* @param pollAddress - The poll's NEM Address
* @return Observable<BroadcastedPoll>
*/
BroadcastedPoll.fromAddress = (pollAddress) => {
return rxjs_1.Observable.fromPromise(BroadcastedPoll.fromAddressPromise(pollAddress));
};
exports.BroadcastedPoll = BroadcastedPoll;
//# sourceMappingURL=poll.js.map