This is a submission for the Build Better on Stellar: Smart Contract Challenge : Create a Tutorial
Your Tutorial
Stellar is a powerful blockchain platform that enables developers to create decentralized applications (dApps) with ease. In this tutorial, we'll walk through the process of building a basic decentralized voting system on Stellar. This project will help you understand key Stellar concepts and give you hands-on experience with smart contract development using Soroban, Stellar's smart contract platform.
Table of Contents
- Introduction to Stellar and Soroban
- Setting Up the Development Environment
- Creating the Voting Contract
- Deploying the Contract
- Building the Frontend
- Testing the dApp
Introduction to Stellar and Soroban
Stellar is an open-source, decentralized protocol for digital currency to fiat money transfers. Soroban is Stellar's smart contract platform, which allows developers to write, deploy, and execute smart contracts on the Stellar network.
Key concepts we'll be using in this tutorial:
- Smart Contracts: Self-executing contracts with the terms directly written into code.
- Accounts: Entities on the Stellar network that can hold balances and send transactions.
- Transactions: Operations that modify the state of the ledger.
Setting Up the Development Environment
Before we start coding, let's set up our development environment. We'll need to install the Stellar CLI, Rust, and create a new Soroban project.
- Install the Stellar CLI:
curl -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
cargo install --locked --version 20.0.0-rc2 soroban-cli
- Create a new Soroban project:
soroban contract init voting-system
cd voting-system
Creating the Voting Contract
Now, let's create our voting contract. We'll define structures for candidates and voters, and implement functions for registering candidates, casting votes, and retrieving results.
Edit the src/lib.rs
file:
#![no_std]
use soroban_sdk::{contractimpl, symbol_short, vec, Env, Symbol, Vec};
#[derive(Clone)]
pub struct Candidate {
name: Symbol,
votes: u32,
}
#[derive(Clone)]
pub struct Voter {
address: Symbol,
has_voted: bool,
}
pub struct VotingContract;
#[contractimpl]
impl VotingContract {
pub fn init(env: Env) -> Vec<Candidate> {
let candidates = vec![&env];
env.storage().set(&symbol_short!("candidates"), &candidates);
candidates
}
pub fn add_candidate(env: Env, name: Symbol) -> Vec<Candidate> {
let mut candidates: Vec<Candidate> = env.storage().get(&symbol_short!("candidates")).unwrap();
candidates.push_back(Candidate { name, votes: 0 });
env.storage().set(&symbol_short!("candidates"), &candidates);
candidates
}
pub fn vote(env: Env, voter: Symbol, candidate_index: u32) -> Vec<Candidate> {
let mut candidates: Vec<Candidate> = env.storage().get(&symbol_short!("candidates")).unwrap();
let mut voters: Vec<Voter> = env.storage().get(&symbol_short!("voters")).unwrap_or(vec![&env]);
// Check if voter has already voted
if let Some(voter_index) = voters.iter().position(|v| v.address == voter) {
if voters.get(voter_index).unwrap().has_voted {
panic!("Voter has already cast a vote");
}
voters.set(voter_index, &Voter { address: voter, has_voted: true });
} else {
voters.push_back(Voter { address: voter, has_voted: true });
}
// Increment vote count for the chosen candidate
let mut candidate = candidates.get(candidate_index).unwrap();
candidate.votes += 1;
candidates.set(candidate_index, &candidate);
env.storage().set(&symbol_short!("candidates"), &candidates);
env.storage().set(&symbol_short!("voters"), &voters);
candidates
}
pub fn get_results(env: Env) -> Vec<Candidate> {
env.storage().get(&symbol_short!("candidates")).unwrap()
}
}
This contract defines four main functions:
-
init
: Initializes the contract with an empty list of candidates. -
add_candidate
: Adds a new candidate to the election. -
vote
: Allows a voter to cast a vote for a candidate. -
get_results
: Retrieves the current voting results.
Deploying the Contract
Now that we have our contract, let's deploy it to the Stellar testnet.
- Build the contract:
soroban contract build
- Create a Stellar account for the contract:
soroban config identity generate contract-admin
soroban config network add testnet --rpc-url https://soroban-testnet.stellar.org:443 --network-passphrase "Test SDF Network ; September 2015"
soroban config identity fund contract-admin --network testnet
- Deploy the contract:
soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/voting_system.wasm \
--source contract-admin \
--network testnet
Make note of the contract ID returned by this command, as we'll need it for interacting with the contract.
Building the Frontend
For the frontend, we'll use React with the @stellar/freighter-api
library to interact with the Stellar network. First, set up a new React project:
npx create-react-app voting-dapp
cd voting-dapp
npm install @stellar/freighter-api
Now, let's create a simple frontend for our voting dApp. Replace the content of src/App.js
:
import React, { useState, useEffect } from 'react';
import { isConnected, getPublicKey, signTransaction } from '@stellar/freighter-api';
import { SorobanRpc, Transaction, xdr } from 'stellar-sdk';
const server = new SorobanRpc.Server("https://soroban-testnet.stellar.org:443");
const contractId = 'YOUR_CONTRACT_ID'; // Replace with your deployed contract ID
function App() {
const [candidates, setCandidates] = useState([]);
const [newCandidate, setNewCandidate] = useState('');
const [selectedCandidate, setSelectedCandidate] = useState('');
const [walletConnected, setWalletConnected] = useState(false);
useEffect(() => {
checkWalletConnection();
fetchCandidates();
}, []);
const checkWalletConnection = async () => {
const connected = await isConnected();
setWalletConnected(connected);
};
const fetchCandidates = async () => {
try {
const result = await server.invokeHostFunction({
contractId: contractId,
functionName: 'get_results',
args: []
});
setCandidates(result);
} catch (error) {
console.error('Error fetching candidates:', error);
}
};
const addCandidate = async () => {
if (!newCandidate) return;
try {
await server.invokeHostFunction({
contractId: contractId,
functionName: 'add_candidate',
args: [xdr.ScSymbol.fromString(newCandidate)]
});
setNewCandidate('');
fetchCandidates();
} catch (error) {
console.error('Error adding candidate:', error);
}
};
const vote = async () => {
if (!selectedCandidate) return;
try {
const publicKey = await getPublicKey();
const transaction = await server.invokeHostFunction({
contractId: contractId,
functionName: 'vote',
args: [
xdr.ScSymbol.fromString(publicKey),
xdr.ScSymbol.fromString(selectedCandidate)
]
});
const signedTransaction = await signTransaction(transaction.toXDR());
await server.sendTransaction(signedTransaction);
fetchCandidates();
} catch (error) {
console.error('Error voting:', error);
}
};
return (
<div className="App">
<h1>Stellar Voting dApp</h1>
{walletConnected ? (
<>
<h2>Add Candidate</h2>
<input
type="text"
value={newCandidate}
onChange={(e) => setNewCandidate(e.target.value)}
/>
<button onClick={addCandidate}>Add Candidate</button>
<h2>Vote</h2>
<select onChange={(e) => setSelectedCandidate(e.target.value)}>
<option value="">Select a candidate</option>
{candidates.map((candidate, index) => (
<option key={index} value={candidate.name}>
{candidate.name}
</option>
))}
</select>
<button onClick={vote}>Vote</button>
<h2>Results</h2>
<ul>
{candidates.map((candidate, index) => (
<li key={index}>
{candidate.name}: {candidate.votes} votes
</li>
))}
</ul>
</>
) : (
<p>Please connect your Stellar wallet to use this dApp.</p>
)}
</div>
);
}
export default App;
This React component provides a simple interface for adding candidates, voting, and viewing results. It uses the Freighter API to interact with the user's Stellar wallet and the stellar-sdk
to communicate with the Soroban RPC server.
Testing the dApp
To test the dApp:
- Start the React development server:
npm start
Open your browser and navigate to
http://localhost:3000
.Connect your Stellar wallet (e.g., Freighter) to the testnet.
Add candidates, cast votes, and view the results.
Some key takeaways:
- Soroban provides a powerful platform for building smart contracts on Stellar.
- The Stellar SDK and Freighter API make it easy to interact with Stellar contracts from web applications.
- Decentralized voting systems can provide transparency and immutability to election processes.
As you continue to explore Stellar development, you can consider adding more features to this dApp, such as:
- Implementing vote delegation
- Adding time limits for voting periods
- Creating different types of voting systems (e.g., ranked choice)
What I Created
This is a tutorial on building a decentralized voting system on the Stellar network. It:
- Introduces developers to Stellar's smart contract platform, Soroban
- Showcases practical use of key Stellar concepts like accounts and transactions
The tutorial serves as a starting point for developers to create more complex applications on the Stellar network, encouraging innovation and adoption of the platform.
Journey
Implementation and Smart Contract Design
For this project, I implemented a basic decentralized voting system on Stellar using Soroban, Stellar's smart contract platform. The smart contract was designed to securely record votes, ensuring that each vote is immutable and transparent. Key features include user registration, vote casting, and vote tallying. Each voter can cast a vote only once, and all votes are recorded on the Stellar blockchain, ensuring transparency and tamper-proof results.
Motivation
The motivation behind this project was to develop a solution that addresses the need for free and fair elections, particularly in my country of origin. By leveraging Stellar's blockchain technology, the aim was to create a voting system that enhances electoral integrity, promotes trust in the democratic process, increases accessibility, and addresses specific electoral challenges.
What I Learned
Through this project, I gained a deeper understanding of the simplicity and power of Soroban for smart contract development. I learned best practices for designing decentralized applications on Stellar and the importance of creating user-friendly interfaces for blockchain applications. Additionally, I identified potential challenges and considerations when implementing voting systems on blockchain, such as security, scalability, and user adoption.
Experience with the Ecosystem
My experience with the Stellar ecosystem was highly positive. The comprehensive documentation and supportive community made it easy to get started with Soroban and smart contract development. The platform's robust features and ease of use allowed me to focus on implementing the core functionalities of the voting system without getting bogged down by technical complexities.
Future Plans
Moving forward, I hope to enhance the voting system by incorporating more advanced features, such as real-time vote tracking and improved security measures. I also plan to explore other use cases for decentralized applications on Stellar, leveraging the platform's capabilities to address various real-world challenges. Additionally, I aim to continue contributing to the Stellar community by sharing my experiences and helping other developers get started with blockchain and smart contract development.
This project has been a rewarding journey, and I am excited about the potential of Stellar and Soroban to drive innovation and positive change in various sectors.
Top comments (0)