Skip to content

The first Solana project: A decentralized voting application

We start the Solana journey with a decentralized voting application, which allows users to put their own idea, poll, or anything else up for vote. Each proposal has a short description, in order to explain to users the poll they are voting on. In addition, votes for and against are processed in the background and data about it is stored on the blockchain. This voting mechanism, although this application is beginner-friendly, can be applied to various types of applications with more or less modifications. Examples of applications are crowdfunding applications, decision-making support in companies, voting for the best solutions or answers, choosing new functionality for an application, choosing the funniest comment… Along with a brief description of the application being written, we will talk about the basic parts and macros used in the program itself, as well as problems I encountered during work. Problems also require a solution, so that is also part of this text and I hope you find it interesting and useful 🙂

This application allows users to:

  • Create proposals with descriptions
  • Users vote for or against
  • Store results on the blockchain , ensuring transparency and immutability

Through this project, we will explore:

  • Solana program components (interaction with PDA, working with instructions, State management)
  • Rust macros used to implement smart contracts
  • Common challenges and their solutions

This app isn’t just a theoretical exercise – it has real-world applications, from managing decentralized administrations (DAOs) to corporate decision-making . If you want to get into the Solana ecosystem or expand your blockchain learning experience , follow along step by step!

Why Choose a Voting Application as Your First Project?

Embarking on your first project in the realm of decentralized applications can be a daunting task, yet selecting a voting application as an entry point into Solana development offers numerous advantages. Voting applications are not only practical but also carry significant relevance in various organizational contexts and governance frameworks. By engaging with this type of project, developers can explore critical functionalities that highlight the core principles of decentralization.

One of the foremost benefits of a voting application is its demonstration of transparency. In both public and private sectors, transparent decision-making processes are essential for building trust. The decentralized nature of such applications ensures that all votes are recorded on a public ledger, making it almost impossible to manipulate results or alter records post facto. This characteristic promotes confidence among participants, establishing a foundation for democratic processes.

Blockchain illustration

Moreover, voting applications encapsulate the principle of immutability, which is pivotal to blockchain systems. Once a vote is cast and recorded, it cannot be changed or deleted. This feature not only bolsters the integrity of the voting process but also aligns with the growing demand for systems that prioritize security against fraud and corruption. By developing a voting application, you can gain firsthand experience in implementing these essential features, which are applicable to various other decentralized applications.

Additionally, working on a voting project allows you to delve into important technical aspects of blockchain technology, such as smart contracts and user authentication mechanisms. Beyond the immediate technical benefits, creating a voting application can also provide invaluable insights into the dynamics of community governance, enabling you to understand how decentralized systems can function effectively within real-world scenarios.

Thus, choosing a voting application for your initial Solana project stands to equip you with a diverse skill set that is not only relevant today but increasingly essential in shaping future governance models.

Solana voting application structure and key Anchor macros

The previous text discussed the basic commands for creating a new project, so I will immediately move on to the structure of the application itself. Using the create-solana-dapp command, a complete skeleton of the application for the backend and frontend is created. The default framework is Anchor, and in the directory with the same name is the file we will work on called lib.rs. It represents the core of the application and what is executed in it will be displayed on the user web interface. The rest of the project structure is explained in detail in the documentation for Solana and Anchor and is part of the training camp if you prefer to follow the video material.

Basic application architecture

In building a decentralized voting application on the Solana blockchain, the Anchor framework is a key tool for rapid development. By creating a project with the create-solana-dapp command, we get a complete structure that includes:

  • 1. lib.rs file – The core of the application where the following are defined:
    • Instructions (transactions)
    • Program state (state)
    • Voting business logic
  • 2. Frontend integration – Display voting results via React or Next.js
  • 3. Solana RPC Connection – Communication with the blockchain

Key Anchor macros for dApp development

Anchor simplifies Solana development through the following basic macros:

  1. #[program] – Defines the main application module with all instructions
  2. #[derive(Accounts)] – Validates input parameters for security
  3. #[account] – Defines the data structure stored on the blockchain
  4. #[account(init)] – Used to create a new account on the Solana network

This approach enables rapid development of decentralized applications with a strong focus on security and efficiency. More detailed information about the Solana and Anchor architectures can be found in the official documentation .

Ethereum and Solana
Ethereum and Solana blockchains

These macros are closely related, and I can visualize their relationship like this: the #[account] macro contains the type of structure that is necessary for the structure in the #[derive(Accounts)] macro, while the function in the #[program] macro requires the existence of the structure in the #[derive(Accounts)] macro.

Challenges, solutions and good practices from the first application

At the beginning of this text, I already listed some of the basic functionalities offered by this decentralized application. However, although simple, it created small headaches. As a newbie to Solana and with modest knowledge of Rust, it took some time to get used to the syntax and environment. So let’s see how it looked.

Error #1: Exceeded timeout of 5000 ms for a test

Even on the latest version of Solana, Anchor and related dependencies, when running the test, the error “Exceeded timeout of 5000 ms for a test” occurred. The test crashed after that 5-second timeout. You can find the post on the Solana Stack Exchange .

What others have answered me and what is generally considered good practice is that all code used in multiple tests should be placed in a beforeAll function. It will only be executed once and will reduce execution time, the code looks cleaner.

I couldn’t solve this error, so I started the project from scratch 🙂

My code:

import { BankrunProvider, startAnchor } from "anchor-bankrun";
import * as anchor from '@coral-xyz/anchor'
import { BN, Program } from '@coral-xyz/anchor'
import { Keypair, PublicKey } from '@solana/web3.js'
import { Votingdapp2 } from '../target/types/votingdapp2'

const IDL = require('../target/idl/votingdapp2.json');

const votingAddress = new PublicKey("coUnmi3oBUtwtd9fjeAvSsJssXh5A5xyPbhpewyzRVF");

describe('Voting', () => {

it('Initialize Poll', async () => {
//const signer = Keypair.generate();

const context = await startAnchor("", [{ name: "voting", programId: votingAddress }], []);

const provider = new BankrunProvider(context);

const votingProgram = new Program<Votingdapp2>(
IDL,
provider,
);

await votingProgram.methods.initializePoll(
new anchor.BN(1),
"What is your favourite type of peanut butter",
new anchor.BN(0),
new anchor.BN(1821246480),
).rpc();

const [pollAddress] = PublicKey.findProgramAddressSync([new anchor.BN(1).toArrayLike(Buffer, 'le', 8)], votingAddress);

const poll = await votingProgram.account.poll.fetch(pollAddress);
console.log(poll);

})

});

Code after implementing recommendations:

import { BankrunProvider, startAnchor } from "anchor-bankrun";
import * as anchor from '@coral-xyz/anchor'
import { Program } from '@coral-xyz/anchor'
import { Keypair, PublicKey } from '@solana/web3.js'
import { Voting } from '../target/types/voting'

const IDL = require('../target/idl/voting.json');

const votingAddress = new PublicKey("FqzkXZdwYjurnUKetJCAvaUw5WAqbwzU6gZEwydeEfqS");

describe('Voting', () => {
// Configure the client to use the local cluster.
let context;
let provider;
let votingProgram: Program;

beforeAll(async () => {
context = await startAnchor("", [{name: "voting", programId: votingAddress}], []);
provider = new BankrunProvider(context);

votingProgram = new Program<Voting>(
IDL,
provider,
);

})

it('Initialize Poll', async () => {

await votingProgram.methods.initializePoll(

new anchor.BN(1),

"What is your favorite type of peanut butter?",

new anchor.BN(0),

new anchor.BN(1847955695),

).rpc();

const [pollAddress] = PublicKey.findProgramAddressSync(

[new anchor.BN(1).toArrayLike(Buffer, 'le', 8)],

votingAddress,

)

const poll = await votingProgram.account.poll.fetch(pollAddress);

console.log(poll);

})

})

Error 2: Argument of type ‘BankrunProvider’ is not assignable to parameter of type ‘Provider’

This error occurred in the smart contract file itself, you can see more about it in the Solana Stack Exchange post. As a temporary solution, I used error suppression by adding the //@ts-ignore directive in front of the line where the error occurred. We will see the final solution for this and the previous error below.

Error 3: File not found: No such file or directory (os error 2)

I spent the most time trying to solve this error, because I didn’t want to give up! 🙂 More about it . It was definitely the one that confused me the most because I didn’t know where exactly in the project to look for what I had missed. The smart contract file looked the same as the lecturer’s in the course, the test too.

Solution for these errors and problems

Due to the incompatibility of the web3.js library with Anchor due to the configuration I chose when creating the project, all three of the above errors occurred. Everything simply resolved itself when I selected web3js-next-tailwind-counter when creating the project , which is an legacy way of creating projects precisely because of the compatibility of Anchor and the web3.js library. The second thing I didn’t do was npm install to pull in the necessary dependencies. I thought that this would happen automatically when creating the project, but know that it wasn’t 😉 The last thing that caused the problem and led to this third error is that the name Counter should be replaced with Vote everywhere in the project’s configuration files. I forgot to do it in one file and that’s the problem. Here’s a little to brag about, complain about, and hopefully help someone not to break their heads over these beginner’s mistakes 🙂 Now let’s move on!

Leave a Reply

Your email address will not be published. Required fields are marked *