Skip to main content

DAO dApp

Description: This is the slightly more complex dApp in this guide. It covers mechanisms of a decentralized autonomous organization (DAO) dApp including creating a proposal, vote on a proposal, fetch all proposals, security considerations, and advanced data structures to ensure a fair and transparent DAO processes.

Purpose: To teach you about complex logic implementation, security best practices, and efficient data management in smart contracts.

Difficulty Level: Difficult

Step 1 - Setting up your development environment​

  • Basic knowledge of terminal commands
  • IDE - Install VS Code

Install Required Packages

Terminal
dotnet new --install AElf.ContractTemplates

AELF.ContractTemplates contains various predefined templates for the ease of developing smart contracts on the aelf blockchain.

  • Install aelf deploy tool
Terminal
dotnet tool install --global aelf.deploy

aelf.deploy is a utility tool for deploying smart contracts on the aelf blockchain. Please remember to export PATH after installing aelf.deploy.

Install Node.js and Yarn

Install aelf-command

Terminal
sudo npm i -g aelf-command

aelf-command is a CLI tool for interacting with the aelf blockchain, enabling tasks like creating wallets and managing transactions. Provide required permissions while installing aelf-command globally.

Install Git

As we will be using a ready made project, we will require git to clone from the project.

Step 2 - Develop Smart Contract​

Project Setup​

  • Open your Terminal.
  • Enter the following command to create a new project folder:
Terminal
mkdir capstone_aelf
cd capstone_aelf
  • Enter this command to create the capstone project.
Terminal
dotnet new aelf -n BuildersDAO

Adding Your Smart Contract Code​

  • Open your project in your favorite IDE (like VSCode).

  • Rename the src/Protobuf/contract/hello_world_contract.proto file to BuildersDAO.proto.

  • After renaming the file, your working directory should look like this.

    img

  • That's it! Your project is now set up and ready to go πŸš€

Defining Methods and Messages​

Let's add the RPC methods and message definitions to our Voting dApp.

  • Open src/Protobuf/contract/BuildersDAO.proto
  • Replace its contents with this code snippet.
src/Protobuf/contract/BuildersDAO.proto
syntax = "proto3";

import "aelf/core.proto";
import "aelf/options.proto";
import "google/protobuf/empty.proto";
import "Protobuf/reference/acs12.proto";

// The namespace of this class
option csharp_namespace = "AElf.Contracts.BuildersDAO";

service BuildersDAO {
// The name of the state class the smart contract is going to use to access
// blockchain state
option (aelf.csharp_state) = "AElf.Contracts.BuildersDAO.BuildersDAOState";
option (aelf.base) = "Protobuf/reference/acs12.proto";

// Actions -> Methods that change state of smart contract
// This method sets up the initial state of our StackUpDAO smart contract
rpc Initialize(google.protobuf.Empty) returns (google.protobuf.Empty);

// This method allows a user to become a member of the DAO by taking in their
// address as an input parameter
rpc JoinDAO(aelf.Address) returns (google.protobuf.Empty);

// This method allows a user to create a proposal for other users to vote on.
// The method takes in a "CreateProposalInput" message which comprises of an
// address, a title, description and a vote threshold (i.e how many votes
// required for the proposal to pass)
rpc CreateProposal(CreateProposalInput) returns (Proposal);

// This method allows a user to vote on proposals towards a specific proposal.
// This method takes in a "VoteInput" message which takes in the address of
// the voter, specific proposal and a boolean which represents their vote
rpc VoteOnProposal(VoteInput) returns (Proposal);

// Views -> Methods that does not change state of smart contract
// This method allows a user to fetch a list of proposals that had been
// created by members of the DAO
rpc GetAllProposals(google.protobuf.Empty) returns (ProposalList) {
option (aelf.is_view) = true;
}

// aelf requires explicit getter methods to access the state value,
// so we provide these three getter methods for accessing the state
// This method allows a user to fetch a proposal by proposalId
rpc GetProposal (google.protobuf.StringValue) returns (Proposal) {
option (aelf.is_view) = true;
}

// This method allows a user to fetch the member count that joined DAO
rpc GetMemberCount (google.protobuf.Empty) returns (google.protobuf.Int32Value) {
option (aelf.is_view) = true;
}

// This method allows a user to check whether this member is exist by address
rpc GetMemberExist (aelf.Address) returns (google.protobuf.BoolValue) {
option (aelf.is_view) = true;
}
}

// Message definitions
message Member {
aelf.Address address = 1;
}

message Proposal {
string id = 1;
string title = 2;
string description = 3;
repeated aelf.Address yesVotes = 4;
repeated aelf.Address noVotes = 5;
string status = 6; // e.g., "IN PROGRESS", "PASSED", "DENIED"
int32 voteThreshold = 7;
}

message CreateProposalInput {
aelf.Address creator = 1;
string title = 2;
string description = 3;
int32 voteThreshold = 4;
}

message VoteInput {
aelf.Address voter = 1;
string proposalId = 2;
bool vote = 3; // true for yes, false for no
}

message MemberList {
repeated Member members = 1;
}

message ProposalList {
repeated Proposal proposals = 1;
}

Understanding the Code​

1. Define Syntax & Imports​
  • proto3 version.
  • Import necessary Protobuf definitions and libraries.
2. RPC Methods​
  • Initialize : Set up initial state
  • JoinDAO : User joins DAO. User's address is the function parameter.
  • CreateProposal : User creates a proposal. User's address , title , description , vote threshold are the function parameter.
  • VoteOnProposal : User votes on a proposal. User's address , proposal vote is the function parameter.
  • GetAllProposals : Fetch list of proposals
3. Getter Methods​
  • GetProposal : Fetch proposal by ID
  • GetMemberCount : Fetch member count
  • GetMemberExist : Check if a member exists by address
4. Message Definitions​
  • Member : DAO member (address)
  • Proposal : Proposal (title, description, votes, status, vote threshold)
  • CreateProposalInput : Fields for creating a proposal (title, description, vote threshold)
  • VoteInput : Fields for voting on a proposal (proposal ID, vote)
  • MemberList : List of DAO members
  • ProposalList : List of proposals

Defining Contract State​

  • Open the src/BuildersDAOState.cs file.
  • Replace its contents with this code snippet.
src/BuildersDAOState.cs
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using AElf.Sdk.CSharp.State;
using AElf.Types;

namespace AElf.Contracts.BuildersDAO
{
// The state class is access the blockchain state
public class BuildersDAOState : ContractState
{
public BoolState Initialized { get; set; }
public MappedState<Address, bool> Members { get; set; }
public MappedState<string, Proposal> Proposals { get; set; }
public Int32State MemberCount { get; set; }
public Int32State NextProposalId { get; set; }
}
}

Understanding the Code​

3. State Variables​
  • Members : Mapping each member to a boolean indicates if they joined the DAO
  • Proposals : Mapping each proposal to an ID for identification and retrieval
  • MemberCountId and NextProposalId : Track total number of members and proposals

Next Step​

  • Implement the logic of our voting smart contract.

Implement Voting Smart Contract Logic​

Checking Smart Contract Logics​

  • Open src/BuildersDAO.cs
  • Replace the existing content with this code snippet.
src/BuildersDAO.cs
using System.Collections.Generic;
using System.Security.Principal;
using AElf.Sdk.CSharp;
using AElf.Sdk.CSharp.State;
using AElf.Types;
using Google.Protobuf.WellKnownTypes;

namespace AElf.Contracts.BuildersDAO
{
public class BuildersDAO : BuildersDAOContainer.BuildersDAOBase
{
const string author = "REPLACE PLACEHOLDER HERE";

// Implement Initialize Smart Contract Logic
public override Empty Initialize(Empty input) { }

// Implement Join DAO Logic
public override Empty JoinDAO(Address input) { }

// Implement Create Proposal Logic
public override Proposal CreateProposal(CreateProposalInput input) { }

// Implement Vote on Proposal Logic
public override Proposal VoteOnProposal(VoteInput input) { }

// Implement Get All Proposals Logic
public override ProposalList GetAllProposals(Empty input) { }

// Implement Get Proposal Logic
public override Proposal GetProposal(StringValue input) { }

// Implement Get Member Count Logic
public override Int32Value GetMemberCount(Empty input) { }

// Implement Get Member Exist Logic
public override BoolValue GetMemberExist(Address input) { }
}
}
danger

Aelf sidechain does not allow duplicate identical smart contracts. Hence, we will be using the author variable as the unique identifier for our voting smart contract in order to deploy the smart contract successfully.

Implementing Initialize Function​

  • Go to the comment Implement Vote on Proposal Logic.
  • Check if the smart contract is already initialized; return if true.
  • Define a hardcoded proposal with necessary parameters.
  • Update the Proposals state variable with the hardcoded proposal and increment the proposalId.
src/BuildersDAO.cs
// Implement Initialize Smart Contract Logic
public override Empty Initialize(Empty input)
{
Assert(!State.Initialized.Value, "already initialized");
var initialProposal = new Proposal
{
Id = "0",
Title = "Proposal #1",
Description = "This is the first proposal of the DAO",
Status = "IN PROGRESS",
VoteThreshold = 1,
};
State.Proposals[initialProposal.Id] = initialProposal;
State.NextProposalId.Value = 1;
State.MemberCount.Value = 0;

State.Initialized.Value = true;

return new Empty();
}

Implementing Join DAO Function​

  • Go to the comment Implement Join DAO Logic
  • Check if the member already exists in the DAO using the Members state variable.
  • If not found, update Members to include the user's address.
  • Increment membersCount to reflect the new member added.

You'll implement this function. Once done, you can proceed to the next page to compare your code with the reference implementation.

src/BuildersDAO.cs
// Implement Join DAO Logic
public override Empty JoinDAO(Address input)
{
// Based on the address, determine whether the address has joined the DAO. If it has, throw an exception
// If the address has not joined the DAO, then join and update the state's value to true
// Read the value of MemberCount in the state, increment it by 1, and update it in the state
// Using 'return null' to ensure the contract compiles successfully. Please update it to the correct return value when implementing
return null;
}

Implementing Create Proposal Function​

  • Go to the comment Implement Create Proposal Logic
  • Check if the user is a DAO member (required to create proposals).
  • Create a new proposal object using fields from CreateProposalInput.
  • Update Proposals with the new proposal, increment NextProposalId, and return the created proposal object.

Now, use the provided code snippet to fill in the CreateProposal function.

src/BuildersDAO.cs
// Implement Create Proposal Logic
public override Proposal CreateProposal(CreateProposalInput input)
{
Assert(State.Members[input.Creator], "Only DAO members can create proposals");
var proposalId = State.NextProposalId.Value.ToString();
var newProposal = new Proposal
{
Id = proposalId,
Title = input.Title,
Description = input.Description,
Status = "IN PROGRESS",
VoteThreshold = input.VoteThreshold,
YesVotes = { }, // Initialize as empty
NoVotes = { }, // Initialize as empty
};
State.Proposals[proposalId] = newProposal;
State.NextProposalId.Value += 1;
return newProposal; // Ensure return
}

Implementing Vote On Proposal Function​

  • Go to the comment Implement Vote on Logic

  • Perform these checks:

    • Verify if the member is a DAO member (required to vote).
    • Confirm if the proposal exists; otherwise, display an error message.
    • Check if the member has already voted on the proposal; members can vote only once.
  • If all checks pass, store the member’s vote and update the proposal state.

  • Update the proposal status based on vote thresholds:

    • If yesVotes reach the threshold, update status to "PASSED".
    • If noVotes reach the threshold, update status to "DENIED".

Now, use the provided code snippet to complete the VoteOnProposal function.

src/BuildersDAO.cs
// Implement Vote on Proposal Logic
public override Proposal VoteOnProposal(VoteInput input)
{
Assert(State.Members[input.Voter], "Only DAO members can vote");
var proposal = State.Proposals[input.ProposalId]; // ?? new proposal
Assert(proposal != null, "Proposal not found");
Assert(
!proposal.YesVotes.Contains(input.Voter) && !proposal.NoVotes.Contains(input.Voter),
"Member already voted"
);

// Add the vote to the appropriate list
if (input.Vote)
{
proposal.YesVotes.Add(input.Voter);
}
else
{
proposal.NoVotes.Add(input.Voter);
}

// Update the proposal in state
State.Proposals[input.ProposalId] = proposal;

// Check if the proposal has reached its vote threshold
if (proposal.YesVotes.Count >= proposal.VoteThreshold)
{
proposal.Status = "PASSED";
}
else if (proposal.NoVotes.Count >= proposal.VoteThreshold)
{
proposal.Status = "DENIED";
}

return proposal;
}

Implementing Get All Proposals Function​

  • Go to the comment Implement Get All Proposals Logic

  • Create a new ProposalList object from the message definition in BuildersDAO.proto.

  • Fetch and iterate through Proposals.

  • Update ProposalList with proposal objects and return the list of proposals.

    • If yesVotes reach the threshold, update status to "PASSED".
    • If noVotes reach the threshold, update status to "DENIED".

You'll implement this function. Once done, you can proceed to the next page to compare your code with the reference implementation.

src/BuildersDAO.cs
// Implement Get All Proposals Logic
public override ProposalList GetAllProposals(Empty input)
{
// Create a new list called ProposalList
// Start iterating through Proposals from index 0 until the value of NextProposalId, read the corresponding proposal, add it to ProposalList, and finally return ProposalList
// Using 'return null' to ensure the contract compiles successfully. Please update it to the correct return value when implementing
return null;
}

Implementing Get Proposal / Get Member Count / Get Member Exist Functions​

1. Get Proposal​
  • Navigate to Implement Get Proposal Logic.
  • Retrieve a proposal by proposalId.
  • Use proposalId as the key to query State.Proposals.
  • Return the corresponding proposal value.
2. Get Member Count​
  • Navigate to Implement Get Member Count Logic.
  • Retrieve the total member count.
  • Return the value of MemberCount from State.
3. Get Member Exist​
  • Navigate to Implement Get Member Exist Logic.
  • Check if a member exists by address.
  • Use address as the key to query State.Members.
  • Return the corresponding existence value.

Implement these methods to access different states effectively in your smart contract.

src/BuildersDAO.cs
// Implement Get Proposal Logic
public override Proposal GetProposal(StringValue input)
{
var proposal = State.Proposals[input.Value];
return proposal;
}

// Implement Get Member Count Logic
public override Int32Value GetMemberCount(Empty input)
{
var memberCount = new Int32Value {Value = State.MemberCount.Value};
return memberCount;
}

// Implement Get Member Exist Logic
public override BoolValue GetMemberExist(Address input)
{
var exist = new BoolValue {Value = State.Members[input]};
return exist;
}

With that, we have implemented all the functionalities of our Voting dApp smart contract.

In the next step, we will compile our smart contract and deploy our written smart contract to the aelf sidechain.

Complete Implementation​

Implementing Join DAO Function​

  • Check Membership : See if the address has already joined the DAO by checking State.Members. Use the Assert method for this verification.
  • Add New Member : If the address isn't a member yet, add it to State.Members and set its value to true.
  • Update Member Count : Increase State.MemberCount by 1 and save the new value.
src/BuildersDAO.cs
public override Empty JoinDAO(Address input)
{
// Based on the address, determine whether the address has joined the DAO. If it has, throw an exception
Assert(!State.Members[input], "Member is already in the DAO");
// If the address has not joined the DAO, then join and update the state's value to true
State.Members[input] = true;
// Read the value of MemberCount in the state, increment it by 1, and update it in the state
var currentCount = State.MemberCount.Value;
State.MemberCount.Value = currentCount + 1;
return new Empty();
}

Implementing Get All Proposals Function​

  • Create a list object called ProposalList.
  • Loop from 0 to the value of State.NextProposalId.
  • In each loop iteration, get the values from State.Proposals and add them to ProposalList.
  • Return ProposalList.
src/BuildersDAO.cs
public override ProposalList GetAllProposals(Empty input)
{
// Create a new list called ProposalList
var proposals = new ProposalList();
// Start iterating through Proposals from index 0 until the value of NextProposalId, read the corresponding proposal, add it to ProposalList, and finally return ProposalList
for (var i = 0; i < State.NextProposalId.Value; i++)
{
var proposalCount = i.ToString();
var proposal = State.Proposals[proposalCount];
proposals.Proposals.Add(proposal);
}
return proposals;
}

Once you've implemented these two methods and run the unit tests again, you should see that all test cases pass.

Building Smart Contract​

  • Build the new code with the following commands inside src folder:
Terminal
dotnet build

You should see BuildersDAO.dll.patched in the directory src/bin/Debug/net.6.0

Step 3 - Deploy Smart Contract​

Create A Wallet​

To send transactions on the aelf blockchain, you must have a wallet.

  • Run this command to create aelf wallet.
Terminal
aelf-command create

result

  • You will be prompted to save your account, please do save your account as shown below:
Terminal
? Save account info into a file? (Y/n) Y

Make sure to choose Y to save your account information.

tip

ℹ️ Note: If you do not save your account information (by selecting n or N), do not export the wallet password. Only proceed to the next step if you have saved your account information.

  • Next, enter and confirm your password. Then export your wallet password as shown below:
Terminal
export WALLET_PASSWORD="YOUR_WALLET_PASSWORD"

Acquire Testnet Tokens (Faucet) for Development​

To deploy smart contracts or execute on-chain transactions on aelf, you'll require testnet ELF tokens.

Get ELF Tokens

1. Get Testnet ELF Tokens:

To receive testnet ELF tokens, run this command after replacing $WALLET_ADDRESS and $WALLET_PASSWORD with your wallet details:

Terminal
export WALLET_ADDRESS="YOUR_WALLET_ADDRESS"
curl -X POST "https://faucet.aelf.dev/api/claim?walletAddress=$WALLET_ADDRESS" -H "accept: application/json" -d ""

2. Check ELF Balance:

To check your ELF balance, use:

Terminal
aelf-command call ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx -a $WALLET_ADDRESS -p $WALLET_PASSWORD -e https://tdvw-test-node.aelf.io GetBalance

You will be prompted for the following:

Terminal
Enter the required param <symbol>: ELF
Enter the required param <owner>: **$WALLET_ADDRESS**

You should see the result displaying your wallet's ELF balance.

Deploy Smart Contract:

The smart contract needs to be deployed on the chain before users can interact with it.

Run the following command to deploy a contract. Remember to export the path of LotteryGame.dll.patched to CONTRACT_PATH.

Terminal
export CONTRACT_PATH=$(find ~+ . -path "*patched*" | head -n 1)
Terminal
aelf-deploy -a $WALLET_ADDRESS -p $WALLET_PASSWORD -c $CONTRACT_PATH -e https://tdvw-test-node.aelf.io/
  • Please wait for approximately 1 to 2 minutes. If the deployment is successful, it will provide you with the contract address. result

  • Copy the smart contract address from the address field result

  • Export your smart contract address:

    Terminal
    export CONTRACT_ADDRESS="YOUR_SMART_CONTRACT_ADDRESS e.g. 2LUmicHyH4RXrMjG4beDwuDsiWJESyLkgkwPdGTR8kahRzq5XS"
tip

ℹ️ Note: You are to copy the smart contract address as we will be referencing it in the next steps!

info

πŸŽ‰ You have successfully deployed your dApp smart contract on the aelf testnet! In the next steps, we will be building the frontend components that allow us to interact with our deployed smart contract!

Step 4 - Interact with Your Deployed Smart Contract​

Project Setup​

Let's start by cloning the frontend project repository from GitHub.

  • Run the following command in the capstone_aelf directory:
Terminal
git clone https://github.com/AElfProject/aelf-samples.git
  • Next, navigate to the frontend project directory with this command:
Terminal
cd aelf-samples/vote/2-dapp
  • Once you're in the 2-dapp directory, open the project with your preferred IDE (e.g., VSCode). You should see the project structure as shown below.

Install necessary libraries​

  • Run this command in the terminal:
Terminal
npm install

We are now ready to build the frontend components of our Voting dApp.

Configure Portkey Provider & Write Connect Wallet Function​

We'll set up our Portkey provider to let users connect their Portkey wallets to our app and interact with our voting smart contract.

  1. Go to the src/useDAOSmartContract.ts file.

  2. In this file, we'll create a component that initializes the Portkey wallet provider and fetches our deployed voting smart contract. This will enable our frontend components to interact with the smart contract for actions like joining the DAO, creating proposals, and more.

  3. Locate the comment Step A - Setup Portkey Wallet Provider and replace the existing useEffect hook with the following code snippet:

useDAOSmartContract.ts
//Step A - Setup Portkey Wallet Provider
useEffect(() => {
(async () => {
if (!provider) return null;

try {
// 1. get the sidechain tDVW using provider.getChain
const chain = await provider?.getChain("tDVW");
if (!chain) throw new Error("No chain");

//Address of DAO Smart Contract
//Replace with Address of Deployed Smart Contract
const address = "2GkJoDicXLqo7cR9YhjCEnCXQt8KUFUTPfCkeJEaAxGFYQo2tb";

// 2. get the DAO contract
const daoContract = chain?.getContract(address);
setSmartContract(daoContract);
} catch (error) {
console.log(error, "====error");
}
})();
}, [provider]);
tip

ℹ️ Note: You are to replace the address placeholder with your deployed voting contract address from "Deploy Voting dApp Smart Contract"!

example: //Replace with Address of Deployed Smart Contract const address = "your_deployed_voting_contract_address";

  • Next, go to the src/HomeDAO.tsx file.

The HomeDAO.tsx file is the landing page of our Voting dApp. It allows users to interact with the deployed smart contract, join the DAO, view proposals, and vote on them.

Before users can interact with the smart contract, we need to write the Connect Wallet function.

Find the comment Step B - Connect Portkey Wallet. Replace the existing connect function with this code snippet:

src/HomeDAO.ts
const connect = async () => {
//Step B - Connect Portkey Wallet
const accounts = await provider?.request({
method: MethodsBase.REQUEST_ACCOUNTS,
});
const account = accounts?.tDVW?.[0];
setCurrentWalletAddress(account);
setIsConnected(true);
alert("Successfully connected");
};

In this code, we fetch the Portkey wallet account using the provider and update the wallet address state variable. An alert notifies the user that their wallet is successfully connected.

With the Connect Wallet function defined, we're ready to write the remaining functions in the next steps.

Write Initialize Smart Contract & Join DAO Functions​

Let's write the Initialize and Join DAO functions.

  1. Find the comment Step C - Write Initialize Smart Contract and Join DAO Logic.

  2. Replace the existing initializeAndJoinDAO function with this code snippet:

src/HomeDAO.ts
const initializeAndJoinDAO = async () => {
//Step C - Write Initialize Smart Contract and Join DAO Logic
try {
const accounts = await provider?.request({
method: MethodsBase.ACCOUNTS,
});
if (!accounts) throw new Error("No accounts");

const account = accounts?.tDVW?.[0];
if (!account) throw new Error("No account");

if (!initialized) {
await DAOContract?.callSendMethod("Initialize", account, {});
setInitialized(true);
alert("DAO Contract Successfully Initialized");
}

await DAOContract?.callSendMethod("JoinDAO", account, account);
setJoinedDAO(true);
alert("Successfully Joined DAO");
} catch (error) {
console.error(error, "====error");
}
};

Here's what the function does:​

  1. Fetches your wallet account using the Portkey wallet provider.

  2. Initializes the DAO smart contract if it hasn't been done already, updating the state and showing a success alert.

  3. Calls the JoinDAO method with your wallet address, updating the state and showing a success alert.

Now, wrap the initializeAndJoinDAO function in the "Join DAO" button to trigger both Initialize and JoinDAO when clicked.

jao-button

Next, we'll write the Create Proposal function.

Write Create Proposal Function​

Let's write the Create Proposal function.

  1. Go to the src/CreateProposal.tsx file. This file is the "Create Proposal" page where users can enter details like the proposal title, description, and vote threshold.

  2. Find the comment Step D - Configure Proposal Form.

  3. Replace the form variable with this code snippet:

src/CreateProposal.tsx
//Step D - Configure Proposal Form
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
address: currentWalletAddress,
title: "",
description: "",
voteThreshold: 0,
},
});
tip

ℹ️ Note: We set currentWalletAddress as the default value because the wallet address is passed from the HomeDAO.tsx page when the user clicks "Create Proposal" on the landing page.

Default value: address: currentWalletAddress

Here's what the function does:​

  1. Initializes a new form variable with default values needed to create a proposal.

  2. Fields include: address , title , description , and vote threshold.

Now your form is ready for users to fill in the necessary details for their proposal.

Now, let's write the Create Proposal function for the form submission.

  1. Scroll down to find the comment Step E - Write Create Proposal Logic.

  2. Replace the onSubmit function with this code snippet:

src/CreateProposal.tsx
// Step E - Write Create Proposal Logic
function onSubmit(values: z.infer<typeof formSchema>) {
const proposalInput: IProposalInput = {
creator: currentWalletAddress,
title: values.title,
description: values.description,
voteThreshold: values.voteThreshold,
};

const createNewProposal = async () => {
try {
await DAOContract?.callSendMethod(
"CreateProposal",
currentWalletAddress,
proposalInput
);

navigate("/");
alert("Successfully created proposal");
} catch (error) {
console.error(error);
}
};

createNewProposal();
}

Here's what the function does:​

  1. Creates a new proposalInput variable with form fields: title , description , and vote threshold.

  2. Invokes the CreateProposal function of the deployed smart contract, using the current wallet address and proposalInput.

  3. If successful, navigates the user to the landing page and shows an alert that the proposal was created.

Next, we'll write the Vote and Fetch Proposal functions to complete the frontend components of our Voting dApp.

Write Vote & Fetch Proposals Function​

In this step, we'll write the Vote and Fetch Proposals functions to complete our Voting dApp's frontend components.

  1. Go to the src/HomeDAO.tsx file and scroll to the Step F - Write Vote Yes Logic comment.

  2. Replace the voteYes function with this code snippet:

src/HomeDAO.tsx
const voteYes = async (index: number) => {
//Step F - Write Vote Yes Logic
try {
const accounts = await provider?.request({
method: MethodsBase.ACCOUNTS,
});

if (!accounts) throw new Error("No accounts");

const account = accounts?.tDVW?.[0];

if (!account) throw new Error("No account");

const createVoteInput: IVoteInput = {
voter: account,
proposalId: index,
vote: true,
};

await DAOContract?.callSendMethod(
"VoteOnProposal",
account,
createVoteInput
);
alert("Voted on Proposal");
setHasVoted(true);
} catch (error) {
console.error(error, "=====error");
}
};

Here's what the function does:​

  1. Takes an index parameter, representing the proposal ID to vote on.

  2. Fetches the wallet address using the Portkey provider.

  3. Creates a createVoteInput parameter with the voter's wallet address, proposal ID, and a true value for a Yes vote..

  4. Calls the VoteOnProposal function from the smart contract.

  5. Updates the state and shows an alert upon a successful vote.

The voteNo function works similarly but sets the vote to false.

  • Scroll down to the Step G - Use Effect to Fetch Proposals comment and replace the useEffect hook with this code snippet:
src/HomeDAO.tsx
useEffect(() => {
// Step G - Use Effect to Fetch Proposals
const fetchProposals = async () => {
try {
const accounts = await provider?.request({
method: MethodsBase.ACCOUNTS,
});

if (!accounts) throw new Error("No accounts");

const account = accounts?.tDVW?.[0];

if (!account) throw new Error("No account");

if (!DAOContract) return;

const proposalResponse = await (DAOContract?.callViewMethod)<IProposals>(
"GetAllProposals",
""
);

setProposals(proposalResponse.data);
alert("Fetched Proposals");
} catch (error) {
console.error(error);
}
};

fetchProposals();
}, [DAOContract, hasVoted, isConnected, joinedDAO]);

Here's what the function does:​

  1. Defines the fetchProposals function that fetches the wallet address.

  2. Calls the GetAllProposals function from the smart contract, returning a list of proposals.

  3. Updates the state and shows an alert once the proposals are fetched.

Now that we've written all the necessary frontend functions and components, we're ready to run the Voting dApp application in the next step.

Run Application​

In this step, we will run the Voting dApp application.

  • To begin, run the following command on your terminal.
Terminal
npm run dev
info

ℹ️ Note: Ensure that you are running this command under the vote/2-dapp folder.

  • You should observe the following as shown below.

    run-app-success

  • Upon clicking on the localhost URL, you should be directed to the StackUpDAO landing page as shown below.

tip

If you are developing and testing this with GitHub codespace, you can use Port Forward to test the web server that is running in codespace, here is the link on how to use Port forward for codespace https://docs.github.com/en/codespaces/developing-in-a-codespace/forwarding-ports-in-your-codespace

  • Usually codespace will automatically forward port, you should see a pop-up message at the bottom right of your codespace browser window as shown in the diagram below:

    open-in-browser

  • Click the link to open the Voting dApp in the browser.

    vote-fe-ui

Create Portkey Wallet​

info

Portkey is the first AA wallet from aelf's ecosystem, migrating users, developers and projects from Web2 to Web3 with DID solution.

Users can swiftly log into Portkey via their Web2 social info with no private keys or mnemonics required. Underpinned by social recovery and decentralized guardian design, Portkey safeguards users' assets from centralized control and theft. Portkey has a unique payment delegation mechanism which enables interested parties to function as delegatees to pay for user activities on users' behalf. This means that users can create accounts for free and fees for other usages may also be covered in Portkey.

Portkey also provides crypto on/off-ramp services, allowing users to exchange fiat with crypto freely. It supports the storage and management of various digital assets such as tokens, NFTs, etc. The compatibility with multi-chains and seamless connection to all kinds of DApps makes Portkey a great way to enter the world of Web3.

With DID solution as its core, Portkey provides both Portkey Wallet and Portkey SDKs.

For more information, you may visit the official documentation for Portkey at https://doc.portkey.finance/.

info

The Portkey extension supports Chrome browser only (for now). Please ensure that you are using Chrome browser. You may download Chrome from https://www.google.com/intl/en_sg/chrome/.

  • Once you have downloaded the extension, you should see the following on your browser as shown below.

    welcome-to-portkey

  • Click on Get Start and you should see the following interface as shown below.

    portkey-login

Sign up

  • Switch to aelf Testnet network by selecting it:

    portkey-switch-to-testnet

danger

Please make sure you are using aelf Testnet in order to be able to receive your testnet tokens from the Faucet.

  • Proceed to sign up with a Google Account or your preferred login method and complete the necessary accounts creation prompts and you should observe the following interface once you have signed up.

    success-login

With that, you have successfully created your very first Portkey wallet within seconds. How easy was that?

info

It is highly recommended to pin the Portkey wallet extension for easier access and navigation to your Portkey wallet!

  • Next, click on β€˜Open Portkey’ and you should now observe the following as shown below.

    portkey-wallet-preview

Connect Portkey Wallet

  • Click on "Connect Wallet" to connect your Portkey wallet. The button will change to "Connected" when the connection is successful.

  • Next, click on "Join DAO". You will be prompted to sign the "Initialize" and "Join DAO" methods, as shown below.

Once you have successfully joined the DAO, you should observe now that the landing page renders the proposal we have defined in our smart contract as shown below.

vote-fe-ui-joineddao

  • Proposal #1 as defined in smart contract
danger

⚠️ Reminder: This proposal has been hard coded within our smart contract to test our vote functionality and is meant for educational purposes! In actual production settings, proposals should not be hardcoded within your smart contract!

  • Let’s test our Vote functionality next.

  • Proceed to click on "Vote Yes" and you should observe the following as shown below prompting you to sign the "Vote Yes" transaction.

    fe-dapp-trans-sign

  • Proceed to click on "Sign".

Upon a successful vote transaction, you should now observe that the proposal status has been updated to "PASSED" as shown below as the Yes vote count has reached the vote threshold.

vote-fe-ui-proposal-voted

  • Proposal status updated to "PASSED" Lastly, we will be creating a proposal to wrap up our demonstration of our Voting dApp.

  • Click on "Create Proposal" for Proceed and you should be directed to the Create Proposal page as shown below.

    fe-dapp-create-proposal

  • Proceed to fill in the following fields under the Create Proposal form:

    • Title - Proposal #2

    • Description - Proposal to onboard Developer DAO

    • Vote Threshold - 10

  • click on "Submit" and you should observe the following as shown below.

    fe-submit-proposal-verify

  • Click on "Sign" to Proceed.

  • Upon a successful proposal creation, you should be directed back to the landing page with the newly created proposal rendered on the landing page as shown below.

    vote-fe-ui-new-proposal

success

πŸŽ‰ Congratulations Learners! You have successfully built your Voting dApp and this is no mean feat!

🎯 Conclusion​

🎊 Congratulations on completing the Voting Contract tutorial! 🎊 You've reached an important milestone in your journey through aelf blockchain development. 🌟

πŸ“š What You've Learned​

Throughout this tutorial, you've gained expertise in:

  • πŸ› οΈ Setting up your development environment for aelf blockchain development.
  • πŸ’» Implementing complex smart contract logic for secure and transparent voting mechanisms.
  • πŸš€ Deploying and interacting with your Voting Smart Contract on the aelf testnet, and integrating it into a frontend application. You've learned how to connect user interfaces with blockchain smart contracts, enabling users to participate in DAOs, create proposals, vote, and track outcomes in real-time.

πŸ” Final Output​

By now, you should have:

  • πŸ“œ Successfully deployed your Voting Smart Contract on the aelf blockchain.
  • πŸŽ‰ Interacted with the contract to join the DAO, create proposals, and participate in voting processes. Seeing your proposals progress based on votes is a testament to your understanding of decentralized decision-making! πŸ†

➑️ What's Next?​

Now that you've mastered the intricacies of voting contracts, it's time to explore more advanced topics or consider enhancing your knowledge in specialized areas of blockchain development. Dive into topics like:

  • πŸ“Š Implementing governance mechanisms for DAOs.
  • πŸ”’ Enhancing security protocols for smart contracts.
  • 🌐 Exploring cross-chain interoperability with aelf.

Keep pushing the boundaries of blockchain technology with aelf. Your journey doesn't end here – it's just the beginning of even more exciting possibilities in decentralized applications and smart contracts. πŸš€

Happy coding and innovating with aelf! 😊