UNPKG

nem-voting

Version:
351 lines 15.8 kB
"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