# Polling app
We will be creating a simple poll application, in which a user can sign in, create polls, cast votes and see voting results. Creating a poll will cost 200 tokens, voting is free, and both actions will be available only for signed in users.
app command will scaffold a project structure for your application in a
voter directory. Make sure to replace
alice with your GitHub username.
voter directory we can see several files and directories:
appcontains files that connect all of the moving parts of your application.
cmdis responsible for
votercliprograms, which respectively allow you to start your application and interact with it.
vuecontains a web user interface for your app, reponsible for everything you see on the screenshot above.
xcontains the main building blocks of you app: modules. Right now we have only one:
Our project's directory contains all the code required to build and launch a blockchain-based app. Let's try launching our app by running starport serve inside our project:
Congratulations! You now have a blockchain application running on your machine in just two commands. It doesn't do anything yet, so let's work on that.
Our voting applications has two types of entities: polls and votes. A poll is a type that has a
title and a list of
# Adding polls
This command generated code that handles the creation of
poll items. If we now run
starport serve and visit http://localhost:8080 we will see a form for creating polls. It may take a short while to rebuild the app, so give it a couple of seconds.
Sign in with one of the passwords printed in the console and try creating a poll. You should see a new object created and displayed above the form. You have successfully created an object and stored it on the blockchain!
This, however, does not look and work exactly like we need. We should be able to add option fields (and store them as an array) and they should be displayed as interactive buttons.
Let's take a look at some of the files modified by the
starport type command.
This file contains definition of the
Poll type. We can see that a poll has two fields (creator and ID), which will be created automatically, and two fields (title and options) defined by us. Since we want
Options to be a list of strings, replace
This file defines a message that creates a poll.
To write anything to a blockchain or perform any other state transition a client (web app in our case) makes an HTTP POST request with a title and options to http://localhost:1317/voter/poll endpoint handler for which is defined in
x/voter/client/rest/txPoll.go. The handler creates an unsigned transaction which contains an array of messages. The client then signs the transaction and sends it to http://localhost:1317/txs. The application then processes the transaction by sending each message to a corresponding handler, in our case
x/voter/handlerMessageCreatePoll.go. A handler then calls a
CreatePoll function defined in
x/voter/keeper/poll.go which writes the poll data into the store.
Going back to
MsgCreatePoll.go, we need to make options to be stored as a list instead of a string. Replace
Options string with
Options string in
MsgCreatePoll struct and
options string with
options string in the arguments of
Options string with
Options string in
A user can also interact with our application through a command line interface.
This command will generate a transaction with "create poll" message, sign it using a private key of
user1 (one of two users created by default) and broadcast it to the blockchain.
The only modification we need to make is to change a line that reads arguments from the console:
argsOptions := args[1:len(args)]. This will assume that all arguments after the first one represent a list of options.
Now that we have made all the necessary changes to our app, let's take a look at the client-side application.
# Front-end application
Starport has generated a basic front-end for our application. For convenience Vue.js framework is used with Vuex for state management, but since all features of our application are exposed through an HTTP API, clients can be built using any language or framework.
We'll be mostly interested in
vue/src/views directory, which contains page templates of our app,
vue/src/store/index.js handles sending transactions and receiving data from our blockchain and
vue/src/components directory, which contains components, like buttons and forms.
vue/src/store/index.js we import CosmJS, a library for handling wallets, creating, signing and broadcasting transactions and define a Vuex store. We'll use
entitySubmit function for sending data to our blockchain (like a JSON representing a newly created poll),
entityFetch for requesting a list of polls and
accountUpdate to fetch information about our token balance.
Since we don't need the default form component replace
<type-list /> inside of
vue/src/views/Index.vue with a new component
<poll-form /> that will be created in a new file at
Poll form component has an input for title and a list of inputs for options. Clicking "Add option" button adds an empty input and clicking "Create poll" sends, creates and broadcasts a transaction with a "create poll" message.
Refresh the page, sign in with a password and create a new poll. It takes a couple of seconds to process a transaction. Now, if you visit http://localhost:1317/voter/poll you should see a list of polls (this endpoint is defined in
# Adding votes
A vote type contains poll ID and a value (string representation of the selected option).
<poll-list /> into the
vue/src/view/Index.vue file after the poll form component. Then make a new component at
vue/src/components/PollList.vue and add the following:
PollList component lists for every poll, all the options for that poll, as buttons. Selecting an option triggers a
submit method that broadcasts a transaction with a "create vote" message and fetches data back from our application.
By now should be able to see the same UI as in the first screenshot. Try creating polls and casting votes. You may notice that it's possible to cast multiple votes for one poll. This is not what we want, so let's fix this behaviour.
# Casting votes only once
To fix this issue we first have to understand how data is stored in our application.
We can think of our data storage as a lexicographically ordered key value store. You can loop through the entries, filter by key prefix, add, update and delete entries. It is easier to visualize the store as JSON:
vote- are prefixes. They are added to keys for ease of filtering. By convention, prefixes are defined in
Whenever a user casts a vote, a new "create vote" message is handled by a handler and is passed to a keeper. Keeper takes a
vote- prefix, adds a UUID (unique to every message) and uses this string as a key.
These strings are unique and we get duplicate votes. To fix that we need to make sure a keeper records every vote only once by choosing the right key. In our case, we can use poll ID and creator address to make sure that one user can cast only one vote per poll.
Restart the application and try voting multiple times on a single poll, you'll see you can vote as many times as you want but only your most recent vote is counted.
# Introducing a fee for creating polls
Let's make it so that creating a poll costs 200 tokens.
This feature is very easy to add. We already require users to have accounts registered, and each user has tokens on balance. The only thing we need to do is to send coins from user's account to a module account before we create a poll.
Add the code above before
k.CreatePoll(ctx, poll). This way, if a user does not have enough tokens, the application will raise an error and will not proceed to creating a poll. Make sure to add
"github.com/tendermint/tendermint/crypto" to the import statement (if your text editor didn't do that for you).
Now, restart the app and try creating several polls to see how this affects your token balance.