{% note alert %}
This smart contract is presented here as an example, do not use it for production without prior verification.
{% endnote %}
If you don't have a local GlobalForce node installed, use the deployment tools at docs.globalforce.io
polleos is an GlobalForce smart contract for creating and managing decentralized polls. It supports both:
Download pollgf.hpp
Download pollgf.cpp
Download pollgf.abi
Token-weighted polls (voting power based on user's token balance)
All polls, options, and votes are stored transparently on-chain, providing auditable, trustless community decision-making.
Anyone can create polls with arbitrary text questions and custom voting options
Support for standard and token-weighted polls
Standard poll: each voter gets one vote
Token poll: each voter’s weight equals their balance of a specified token
Prevents double voting (each user can vote only once per poll)
All data on-chain: poll creation, vote casting, and tallies are persisted in GF tables
Create a new, standard (non-token-weighted) poll.
newpoll(string question, account_name creator, vector<string> options)
question: The poll's question (string)
creator: Account paying for RAM (who creates the poll)
options: List of option strings
Create a new poll where vote weights are based on token balance.
newtokenpoll(string question, account_name payer, vector<string> options, extended_symbol token)
question: The poll's question
payer: Account paying for RAM (who creates the poll)
options: List of option strings
token: The token used to weight votes (contract and symbol)
Vote in a poll.
vote(uint64_t poll_id, account_name voter, uint8_t option_id)
poll_id: ID of the poll
voter: Voter's account name
option_id: Index of the chosen option
Each poll has an ID, question, and options (max 255).
Standard poll: Each voter can cast one vote for one option.
Token-weighted poll: Each voter can cast one vote; vote's weight = balance of specified token at voting time.
Users cannot vote more than once in the same poll (double-voting is rejected).
poll (scope: contract): Stores all poll definitions (ID, question, options, results, token info).
votes (scope: voter): Stores which poll(s) a user has voted in and for which option.
All votes and poll results are available for public audit.
Create a standard poll:
cleos push action polleosacc newpoll '["Who is best?", "alice", ["Alice", "Bob", "Charlie"]]' -p alice
Create a token-weighted poll:
cleos push action polleosacc newtokenpoll '["Favorite EOSIO wallet?", "bob", ["Anchor", "Scatter"], ["4,EOS","eosio.token"]]' -p bob
Vote in poll #1, option 2, as alice:
cleos push action polleosacc vote '[1, "alice", 2]' -p alice
Each user can vote only once per poll (enforced on-chain).
For token-weighted polls, the user's token balance at the time of voting is used.
Only tokens with a valid contract and symbol can be used.
All results and tallies are on-chain for auditability.
Requirements GlobalForce/EOSIO/Antelope blockchain Standard eosio.token contract deployed for token-weighted polls
Add poll closing times and restrict voting after close
Allow poll creators to manage or remove polls
Add support for anonymous voting (via zero-knowledge or shielded tokens)
Integrate with UI or DAOs
MIT or similarly permissive license.
#pragma once
#include <eosiolib/eosio.hpp>
#include <cmath>
#include "eosio.token.hpp"
/**
* @class pollgf
* EOSIO voting contract, supporting normal and token-weighted polls.
*/
class pollgf : public eosio::contract {
public:
typedef uint64_t poll_id_t; // Type for poll IDs
typedef std::vector<std::string> option_names_t; // List of option strings
typedef eosio::extended_symbol token_info_t; // Token info (symbol+contract)
typedef uint8_t option_id_t; // Option index type
pollgf(account_name contract_name)
: eosio::contract(contract_name), _polls(contract_name, contract_name) {}
/**
* @struct option
* Stores the name of a voting option.
*/
struct option {
std::string name;
option(std::string name) : name(name) {}
option() {}
EOSLIB_SERIALIZE(option, (name))
};
/**
* @struct option_result
* Extends option by also tracking the number of votes.
*/
struct option_result : option {
double votes = 0; // Vote total (can be fractional for token-weighted polls)
option_result(const std::string& name, uint64_t votes) : option(name), votes(votes) {}
option_result(const std::string& name) : option_result(name, 0) {}
option_result() {}
EOSLIB_SERIALIZE(option_result, (name)(votes))
};
typedef std::vector<option_result> option_results;
/**
* @struct poll
* Stores a poll (question, options, vote tallies, etc).
*/
//@abi table
struct poll {
poll_id_t id; // Poll unique id
std::string question; // Poll question text
option_results results; // Array of results (option name + vote tally)
bool is_token_poll = false; // True if poll is token-weighted
token_info_t token; // Token info (if token-weighted)
uint64_t primary_key() const { return id; }
// Used for reverse order lookup/indexing (optional)
uint64_t get_reverse_key() const { return ~id; }
// Initializes poll object with all values and options.
void set(poll_id_t id, const std::string& question,
const option_names_t& options, bool is_token_poll,
token_info_t token);
EOSLIB_SERIALIZE(poll, (id)(question)(results)(is_token_poll)(token))
};
/**
* @struct poll_vote
* Stores a user's vote in a poll (per user per poll).
*/
//@abi table votes
struct poll_vote {
poll_id_t poll_id; // The poll id this vote belongs to
option_id_t option_id; // Chosen option index
uint64_t primary_key() const { return poll_id; }
EOSLIB_SERIALIZE(poll_vote, (poll_id)(option_id))
};
// Table of polls, with a reverse index (not strictly needed)
typedef eosio::multi_index<N(poll), poll,
eosio::indexed_by<N(reverse),
eosio::const_mem_fun<poll, uint64_t, &poll::get_reverse_key>
>
> poll_table;
// Table of votes for each user (scope = user account)
typedef eosio::multi_index<N(votes), poll_vote> vote_table;
//@abi action
void newpoll(const std::string& question, account_name creator,
const std::vector<std::string>& options);
//@abi action
void newtokenpoll(const std::string& question, account_name payer,
const std::vector<std::string>& options,
token_info_t token);
//@abi action
void vote(poll_id_t id, account_name voter, option_id_t option_id);
private:
// Stores poll on-chain.
void store_poll(const std::string& question, account_name owner,
const option_names_t& options,
bool is_token_poll, token_info_t token);
// Stores a user's vote and increments result.
void store_vote(const poll& p, vote_table& votes, option_id_t option_id, double weight);
// Stores a user's vote, using their token balance as weight.
void store_token_vote(const poll& p, vote_table& votes, option_id_t option_id);
// Converts an EOSIO asset to a weight (as a floating-point number)
double to_weight(const eosio::asset& stake) {
return stake.amount / std::pow(10, stake.symbol.precision());
}
poll_table _polls; // Main on-chain poll storage table
};
/**
* pollgf - A simple on-chain voting (poll) smart contract for EOSIO.
*
* This contract allows anyone to create polls with multiple options, and for users to vote.
* Polls can be standard (1 account = 1 vote) or "token-weighted" (vote weight is based on user's token balance).
* Votes and poll data are stored on-chain for auditability and transparency.
*/
#include "pollgf.hpp"
#include <limits>
/**
* @brief Sets up a poll object with the specified options and properties.
* @param id The poll's unique ID.
* @param question The poll question.
* @param options The list of options for voting.
* @param is_token_poll True if this is a token-weighted poll.
* @param token Information about the token for weighting, if needed.
*/
void pollgf::poll::set(pollgf::poll_id_t id, const std::string& question,
const option_names_t& options, bool is_token_poll,
token_info_t token) {
eosio_assert(!question.empty(), "Question can't be empty");
this->id = id;
this->question = question;
this->is_token_poll = is_token_poll;
this->token = token;
// Prepare results array for each voting option.
results.resize(options.size());
std::transform(options.begin(), options.end(), results.begin(),
[&](std::string str) {
eosio_assert(!str.empty(), "Option names can't be empty");
return option_result(str);
});
}
/**
* @brief Stores a new poll in the contract's poll table.
* @param question The poll question.
* @param poll_owner Who pays RAM for this poll (creator).
* @param options The list of voting options.
* @param is_token_poll Whether the poll is token-weighted.
* @param token Token info, if needed.
*/
void pollgf::store_poll(const std::string& question, account_name poll_owner,
const option_names_t& options,
bool is_token_poll, token_info_t token) {
poll_id_t id;
eosio_assert(options.size() < std::numeric_limits<option_id_t>::max(),
"Too many options");
_polls.emplace(poll_owner, [&](poll& p) {
id = _polls.available_primary_key();
p.set(id, question, options, is_token_poll, token);
});
eosio::print("Poll stored with id: ", id);
}
/**
* @brief Stores a user's vote in a poll (with explicit vote weight).
* Also increments the selected option's result.
* @param p The poll object.
* @param votes The vote table for the voter.
* @param option_id The selected option's ID.
* @param weight The weight of the vote (1 for normal, token balance for token polls).
*/
void pollgf::store_vote(const pollgf::poll& p, pollgf::vote_table& votes,
option_id_t option_id, double weight) {
eosio_assert(weight > 0, "Vote weight cannot be less than 0. Contract logic issue");
// Voter (votes.get_scope()) pays for RAM.
votes.emplace(votes.get_scope(), [&](poll_vote& v) {
v.poll_id = p.id;
v.option_id = option_id;
});
_polls.modify(p, votes.get_scope(), [&](poll& p) {
p.results[option_id].votes += weight;
});
}
/**
* @brief Stores a user's token-weighted vote in a poll.
* Checks token balance, then records vote.
* @param p The poll object.
* @param votes The vote table for the voter.
* @param option_id The selected option's ID.
*/
void pollgf::store_token_vote(const pollgf::poll& p, pollgf::vote_table& votes,
option_id_t option_id) {
account_name voter = votes.get_scope();
eosio::token token(p.token.contract);
// Will fail if voter has no tokens
eosio::asset balance = token.get_balance(voter, p.token.name());
// Validate token balance
eosio_assert(balance.is_valid(), "Balance of voter account is invalid. Something is wrong with token contract.");
eosio_assert(balance.amount > 0, "Voter must have more than 0 tokens to participate in a poll!");
// Store vote with token balance as weight
store_vote(p, votes, option_id, to_weight(balance));
}
/**
* @brief Create a new standard (non-token-weighted) poll.
* @param question The poll question.
* @param payer Account paying for RAM.
* @param options The list of voting options.
* @abi action
*/
void pollgf::newpoll(const std::string& question, account_name payer,
const option_names_t& options) {
store_poll(question, payer, options, false, token_info_t());
}
/**
* @brief Create a new token-weighted poll.
* @param question The poll question.
* @param owner Account paying for RAM.
* @param options The list of voting options.
* @param token_inf Info about the token to use for vote weighting.
* @abi action
*/
void pollgf::newtokenpoll(const std::string& question, account_name owner,
const option_names_t& options, token_info_t token_inf) {
eosio::token token(token_inf.contract);
eosio_assert(token.exists(token_inf.name()), "This token does not exist");
store_poll(question, owner, options, true, token_inf);
}
/**
* @brief Cast a vote in a poll.
* Checks for double-voting and option validity, then stores the vote.
* @param id Poll id.
* @param voter Voter's account.
* @param option_id Chosen option's index.
* @abi action
*/
void pollgf::vote(pollgf::poll_id_t id, account_name voter, option_id_t option_id) {
eosio::require_auth(voter);
const poll & p = _polls.get(id, "Poll with this id does not exist");
eosio_assert(option_id < p.results.size(), "Option with this id does not exist");
vote_table votes(get_self(), voter);
eosio_assert(votes.find(p.id) == votes.end(), "This account has already voted in this poll");
if (p.is_token_poll)
store_token_vote(p, votes, option_id);
else
store_vote(p, votes, option_id, 1);
eosio::print("Vote stored!");
}
// Macro to register the contract's actions
EOSIO_ABI(pollgf, (newpoll)(newtokenpoll)(vote))
{
"types": [{
"new_type_name": "poll_id_t",
"type": "uint64"
},{
"new_type_name": "option_results",
"type": "option_result[]"
},{
"new_type_name": "token_info_t",
"type": "extended_symbol"
},{
"new_type_name": "symbol_name",
"type": "symbol"
}
],
"structs": [{
"name": "option",
"base": "",
"fields": [{
"name": "name",
"type": "string"
}
]
},{
"name": "option_result",
"base": "option",
"fields": [{
"name": "votes",
"type": "float64"
}
]
},{
"name": "symbol_type",
"base": "",
"fields": [{
"name": "value",
"type": "symbol_name"
}
]
},{
"name": "extended_symbol",
"base": "symbol_type",
"fields": [{
"name": "contract",
"type": "name"
}
]
},{
"name": "poll",
"base": "",
"fields": [{
"name": "id",
"type": "poll_id_t"
},{
"name": "question",
"type": "string"
},{
"name": "results",
"type": "option_results"
},{
"name": "is_token_poll",
"type": "bool"
},{
"name": "token",
"type": "token_info_t"
}
]
},{
"name": "poll_vote",
"base": "",
"fields": [{
"name": "pollid",
"type": "poll_id_t"
},{
"name": "optionid",
"type": "uint8"
}
]
},{
"name": "newpoll",
"base": "",
"fields": [{
"name": "question",
"type": "string"
},{
"name": "creator",
"type": "name"
},{
"name": "options",
"type": "string[]"
}
]
},{
"name": "newtokenpoll",
"base": "",
"fields": [{
"name": "question",
"type": "string"
},{
"name": "creator",
"type": "name"
},{
"name": "options",
"type": "string[]"
},{
"name": "token",
"type": "token_info_t"
}
]
},{
"name": "vote",
"base": "",
"fields": [{
"name": "id",
"type": "poll_id_t"
},{
"name": "voter",
"type": "name"
},{
"name": "option_id",
"type": "uint8"
}
]
}
],
"actions": [{
"name": "newpoll",
"type": "newpoll",
"ricardian_contract": ""
},{
"name": "newtokenpoll",
"type": "newtokenpoll",
"ricardian_contract": ""
},{
"name": "vote",
"type": "vote",
"ricardian_contract": ""
}
],
"tables": [{
"name": "poll",
"index_type": "i64",
"key_names": [
"id"
],
"key_types": [
"poll_id_t"
],
"type": "poll"
},{
"name": "votes",
"index_type": "i64",
"key_names": [
"id"
],
"key_types": [
"poll_id_t"
],
"type": "poll_vote"
}
],
"ricardian_clauses": []
}
{% note alert %} This smart contract is presented here as an example, do not use it for production without prior verification. {% endnote %} If you don't have a