logo

Vote Contract

Description: This is the most complex contract in this guide. It covers voting mechanisms, security considerations, and advanced data structures to ensure a fair and transparent voting process.

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

  • Install dotnet 6.0 SDK
  • Install aelf contract templates
  • 1
    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
  • 1
    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 Node.js
  • Install aelf-command
  • 1
    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

  • 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:
  • 1
    mkdir capstone_aelf
    2
    cd capstone_aelf
  • Enter this command to create the capstone project.
  • 1
    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.
  • 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.
  • 1
    syntax = "proto3";
    2
    3
    import "aelf/core.proto";
    4
    import "aelf/options.proto";
    5
    import "google/protobuf/empty.proto";
    6
    import "Protobuf/reference/acs12.proto";
    7
    8
    // The namespace of this class
    9
    option csharp_namespace = "AElf.Contracts.BuildersDAO";
    10
    11
    service BuildersDAO {
    12
    // The name of the state class the smart contract is going to use to access
    13
    // blockchain state
    14
    option (aelf.csharp_state) = "AElf.Contracts.BuildersDAO.BuildersDAOState";
    15
    option (aelf.base) = "Protobuf/reference/acs12.proto";
    16
    17
    // Actions -> Methods that change state of smart contract
    18
    // This method sets up the initial state of our StackUpDAO smart contract
    19
    rpc Initialize(google.protobuf.Empty) returns (google.protobuf.Empty);
    20
    21
    // This method allows a user to become a member of the DAO by taking in their
    22
    // address as an input parameter
    23
    rpc JoinDAO(aelf.Address) returns (google.protobuf.Empty);
    24
    25
    // This method allows a user to create a proposal for other users to vote on.
    26
    // The method takes in a "CreateProposalInput" message which comprises of an
    27
    // address, a title, description and a vote threshold (i.e how many votes
    28
    // required for the proposal to pass)
    29
    rpc CreateProposal(CreateProposalInput) returns (Proposal);
    30
    31
    // This method allows a user to vote on proposals towards a specific proposal.
    32
    // This method takes in a "VoteInput" message which takes in the address of
    33
    // the voter, specific proposal and a boolean which represents their vote
    34
    rpc VoteOnProposal(VoteInput) returns (Proposal);
    35
    36
    // Views -> Methods that does not change state of smart contract
    37
    // This method allows a user to fetch a list of proposals that had been
    38
    // created by members of the DAO
    39
    rpc GetAllProposals(google.protobuf.Empty) returns (ProposalList) {
    40
    option (aelf.is_view) = true;
    41
    }
    42
    43
    // aelf requires explicit getter methods to access the state value,
    44
    // so we provide these three getter methods for accessing the state
    45
    // This method allows a user to fetch a proposal by proposalId
    46
    rpc GetProposal (google.protobuf.StringValue) returns (Proposal) {
    47
    option (aelf.is_view) = true;
    48
    }
    49
    50
    // This method allows a user to fetch the member count that joined DAO
    51
    rpc GetMemberCount (google.protobuf.Empty) returns (google.protobuf.Int32Value) {
    52
    option (aelf.is_view) = true;
    53
    }
    54
    55
    // This method allows a user to check whether this member is exist by address
    56
    rpc GetMemberExist (aelf.Address) returns (google.protobuf.BoolValue) {
    57
    option (aelf.is_view) = true;
    58
    }
    59
    }
    60
    61
    // Message definitions
    62
    message Member {
    63
    aelf.Address address = 1;
    64
    }
    65
    66
    message Proposal {
    67
    string id = 1;
    68
    string title = 2;
    69
    string description = 3;
    70
    repeated aelf.Address yesVotes = 4;
    71
    repeated aelf.Address noVotes = 5;
    72
    string status = 6; // e.g., "IN PROGRESS", "PASSED", "DENIED"
    73
    int32 voteThreshold = 7;
    74
    }
    75
    76
    message CreateProposalInput {
    77
    aelf.Address creator = 1;
    78
    string title = 2;
    79
    string description = 3;
    80
    int32 voteThreshold = 4;
    81
    }
    82
    83
    message VoteInput {
    84
    aelf.Address voter = 1;
    85
    string proposalId = 2;
    86
    bool vote = 3; // true for yes, false for no
    87
    }
    88
    89
    message MemberList {
    90
    repeated Member members = 1;
    91
    }
    92
    93
    message ProposalList {
    94
    repeated Proposal proposals = 1;
    95
    }

    Understanding the Code#

  • Define Syntax & Imports
  • proto3 version.
  • Import necessary Protobuf definitions and libraries.
  • 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
  • Getter Methods
  • GetProposal : Fetch proposal by ID
  • GetMemberCount : Fetch member count
  • GetMemberExist : Check if a member exists by address
  • 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.
  • 1
    using System.Collections.Generic;
    2
    using System.Diagnostics.CodeAnalysis;
    3
    using AElf.Sdk.CSharp.State;
    4
    using AElf.Types;
    5
    6
    namespace AElf.Contracts.BuildersDAO
    7
    {
    8
    // The state class is access the blockchain state
    9
    public class BuildersDAOState : ContractState
    10
    {
    11
    public BoolState Initialized { get; set; }
    12
    public MappedState<Address, bool> Members { get; set; }
    13
    public MappedState<string, Proposal> Proposals { get; set; }
    14
    public Int32State MemberCount { get; set; }
    15
    public Int32State NextProposalId { get; set; }
    16
    }
    17
    }

    Understanding the Code#

  • 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.
  • 1
    using System.Collections.Generic;
    2
    using System.Security.Principal;
    3
    using AElf.Sdk.CSharp;
    4
    using AElf.Sdk.CSharp.State;
    5
    using AElf.Types;
    6
    using Google.Protobuf.WellKnownTypes;
    7
    8
    namespace AElf.Contracts.BuildersDAO
    9
    {
    10
    public class BuildersDAO : BuildersDAOContainer.BuildersDAOBase
    11
    {
    12
    const string author = "REPLACE PLACEHOLDER HERE";
    13
    14
    // Implement Initialize Smart Contract Logic
    15
    public override Empty Initialize(Empty input) { }
    16
    17
    // Implement Join DAO Logic
    18
    public override Empty JoinDAO(Address input) { }
    19
    20
    // Implement Create Proposal Logic
    21
    public override Proposal CreateProposal(CreateProposalInput input) { }
    22
    23
    // Implement Vote on Proposal Logic
    24
    public override Proposal VoteOnProposal(VoteInput input) { }
    25
    26
    // Implement Get All Proposals Logic
    27
    public override ProposalList GetAllProposals(Empty input) { }
    28
    29
    // Implement Get Proposal Logic
    30
    public override Proposal GetProposal(StringValue input) { }
    31
    32
    // Implement Get Member Count Logic
    33
    public override Int32Value GetMemberCount(Empty input) { }
    34
    35
    // Implement Get Member Exist Logic
    36
    public override BoolValue GetMemberExist(Address input) { }
    37
    }
    38
    }

    Implementing Initialize Function#

  • Go to the comment Implement Initialize Smart Contract 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.
  • 1
    // Implement Initialize Smart Contract Logic
    2
    public override Empty Initialize(Empty input)
    3
    {
    4
    Assert(!State.Initialized.Value, "already initialized");
    5
    var initialProposal = new Proposal
    6
    {
    7
    Id = "0",
    8
    Title = "Proposal #1",
    9
    Description = "This is the first proposal of the DAO",
    10
    Status = "IN PROGRESS",
    11
    VoteThreshold = 1,
    12
    };
    13
    State.Proposals[initialProposal.Id] = initialProposal;
    14
    State.NextProposalId.Value = 1;
    15
    State.MemberCount.Value = 0;
    16
    17
    State.Initialized.Value = true;
    18
    19
    return new Empty();
    20
    }

    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.

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

    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.

    1
    // Implement Create Proposal Logic
    2
    public override Proposal CreateProposal(CreateProposalInput input)
    3
    {
    4
    Assert(State.Members[input.Creator], "Only DAO members can create proposals");
    5
    var proposalId = State.NextProposalId.Value.ToString();
    6
    var newProposal = new Proposal
    7
    {
    8
    Id = proposalId,
    9
    Title = input.Title,
    10
    Description = input.Description,
    11
    Status = "IN PROGRESS",
    12
    VoteThreshold = input.VoteThreshold,
    13
    YesVotes = { }, // Initialize as empty
    14
    NoVotes = { }, // Initialize as empty
    15
    };
    16
    State.Proposals[proposalId] = newProposal;
    17
    State.NextProposalId.Value += 1;
    18
    return newProposal; // Ensure return
    19
    }

    Implementing Vote On Proposal Function#

  • Go to the comment Implement Vote on Logic
  • Perform these checks:
  • If all checks pass, store the member’s vote and update the proposal state.
  • Update the proposal status based on vote thresholds:
  • Now, use the provided code snippet to complete the VoteOnProposal function.

    1
    // Implement Vote on Proposal Logic
    2
    public override Proposal VoteOnProposal(VoteInput input)
    3
    {
    4
    Assert(State.Members[input.Voter], "Only DAO members can vote");
    5
    var proposal = State.Proposals[input.ProposalId]; // ?? new proposal
    6
    Assert(proposal != null, "Proposal not found");
    7
    Assert(
    8
    !proposal.YesVotes.Contains(input.Voter) && !proposal.NoVotes.Contains(input.Voter),
    9
    "Member already voted"
    10
    );
    11
    12
    // Add the vote to the appropriate list
    13
    if (input.Vote)
    14
    {
    15
    proposal.YesVotes.Add(input.Voter);
    16
    }
    17
    else
    18
    {
    19
    proposal.NoVotes.Add(input.Voter);
    20
    }
    21
    22
    // Update the proposal in state
    23
    State.Proposals[input.ProposalId] = proposal;
    24
    25
    // Check if the proposal has reached its vote threshold
    26
    if (proposal.YesVotes.Count >= proposal.VoteThreshold)
    27
    {
    28
    proposal.Status = "PASSED";
    29
    }
    30
    else if (proposal.NoVotes.Count >= proposal.VoteThreshold)
    31
    {
    32
    proposal.Status = "DENIED";
    33
    }
    34
    35
    return proposal;
    36
    }

    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.
  • You'll implement this function. Once done, you can proceed to the next page to compare your code with the reference implementation.

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

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

  • 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.
  • Get Member Count
  • Navigate to Implement Get Member Count Logic.
  • Retrieve the total member count.
  • Return the value of MemberCount from State.
  • 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.

    1
    // Implement Get Proposal Logic
    2
    public override Proposal GetProposal(StringValue input)
    3
    {
    4
    var proposal = State.Proposals[input.Value];
    5
    return proposal;
    6
    }
    7
    8
    // Implement Get Member Count Logic
    9
    public override Int32Value GetMemberCount(Empty input)
    10
    {
    11
    var memberCount = new Int32Value {Value = State.MemberCount.Value};
    12
    return memberCount;
    13
    }
    14
    15
    // Implement Get Member Exist Logic
    16
    public override BoolValue GetMemberExist(Address input)
    17
    {
    18
    var exist = new BoolValue {Value = State.Members[input]};
    19
    return exist;
    20
    }

    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.
  • 1
    public override Empty JoinDAO(Address input)
    2
    {
    3
    // Based on the address, determine whether the address has joined the DAO. If it has, throw an exception
    4
    Assert(!State.Members[input], "Member is already in the DAO");
    5
    // If the address has not joined the DAO, then join and update the state's value to true
    6
    State.Members[input] = true;
    7
    // Read the value of MemberCount in the state, increment it by 1, and update it in the state
    8
    var currentCount = State.MemberCount.Value;
    9
    State.MemberCount.Value = currentCount + 1;
    10
    return new Empty();
    11
    }

    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.
  • 1
    public override ProposalList GetAllProposals(Empty input)
    2
    {
    3
    // Create a new list called ProposalList
    4
    var proposals = new ProposalList();
    5
    // Start iterating through Proposals from index 0 until the value of NextProposalId, read the corresponding proposal, add it to ProposalList, and finally return ProposalList
    6
    for (var i = 0; i < State.NextProposalId.Value; i++)
    7
    {
    8
    var proposalCount = i.ToString();
    9
    var proposal = State.Proposals[proposalCount];
    10
    proposals.Proposals.Add(proposal);
    11
    }
    12
    return proposals;
    13
    }

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

    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.

    1
    aelf-command create

    You will be prompted to save your account, please do save your account as shown below:

    1
    ? Save account info into a file? (Y/n) Y

    Next, enter and confirm your password. Then export your wallet password as shown below:

    1
    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

  • CLI
  • Web
  • Run the following command to get testnet ELF tokens from faucet. Remember to either export your wallet address and wallet password or replace π‘Šπ΄πΏπΏπΈπ‘‡π΄π·π·π‘…πΈπ‘†π‘†π‘Žπ‘›π‘‘WALLETADDRESSandWALLET_ADDRESS with your wallet address and wallet password respectively.

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

    To check your wallet's current ELF balance:

    1
    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:

    1
    Enter the required param <symbol>: ELF
    2
    Enter the required param <owner>: $WALLET_ADDRESS

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

    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.

    1
    export CONTRACT_PATH=$(find ~+ . -path "*patched*" | head -n 1)
    1
    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.

    Export your smart contract address:

    1
    export CONTRACT_ADDRESS="YOUR_SMART_CONTRACT_ADDRESS e.g. 2LUmicHyH4RXrMjG4beDwuDsiWJESyLkgkwPdGTR8kahRzq5XS"

    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

    1
    git clone https://github.com/AElfProject/vote-contract-frontend.git
  • Next, navigate to the frontend project directory with this command:
  • Terminal

    1
    cd vote-contract-frontend
  • Once you're in the vote-contract-frontend 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

    1
    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.

  • Go to the src/useDAOSmartContract.ts file.
  • 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.
  • Locate the comment Step A - Setup Portkey Wallet Provider and replace the existing useEffect hook with the following code snippet:
  • src/useDAOSmartContract.ts

    1
    //Step A - Setup Portkey Wallet Provider
    2
    useEffect(() => {
    3
    (async () => {
    4
    if (!provider) return null;
    5
    6
    try {
    7
    // 1. get the sidechain tDVW using provider.getChain
    8
    const chain = await provider?.getChain("tDVW");
    9
    if (!chain) throw new Error("No chain");
    10
    11
    //Address of DAO Smart Contract
    12
    //Replace with Address of Deployed Smart Contract
    13
    const address = "2GkJoDicXLqo7cR9YhjCEnCXQt8KUFUTPfCkeJEaAxGFYQo2tb";
    14
    15
    // 2. get the DAO contract
    16
    const daoContract = chain?.getContract(address);
    17
    setSmartContract(daoContract);
    18
    } catch (error) {
    19
    console.log(error, "====error");
    20
    }
    21
    })();
    22
    }, [provider]);
  • 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

    1
    const connect = async () => {
    2
    //Step B - Connect Portkey Wallet
    3
    const accounts = await provider?.request({
    4
    method: MethodsBase.REQUEST_ACCOUNTS,
    5
    });
    6
    const account = accounts?.tDVW?.[0];
    7
    setCurrentWalletAddress(account);
    8
    setIsConnected(true);
    9
    alert("Successfully connected");
    10
    };

    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.

  • Find the comment Step C - Write Initialize Smart Contract and Join DAO Logic.
  • Replace the existing initializeAndJoinDAO function with this code snippet:
  • src/HomeDAO.ts

    1
    const initializeAndJoinDAO = async () => {
    2
    //Step C - Write Initialize Smart Contract and Join DAO Logic
    3
    try {
    4
    const accounts = await provider?.request({
    5
    method: MethodsBase.ACCOUNTS,
    6
    });
    7
    if (!accounts) throw new Error("No accounts");
    8
    9
    const account = accounts?.tDVW?.[0];
    10
    if (!account) throw new Error("No account");
    11
    12
    if (!initialized) {
    13
    await DAOContract?.callSendMethod("Initialize", account, {});
    14
    setInitialized(true);
    15
    alert("DAO Contract Successfully Initialized");
    16
    }
    17
    18
    await DAOContract?.callSendMethod("JoinDAO", account, account);
    19
    setJoinedDAO(true);
    20
    alert("Successfully Joined DAO");
    21
    } catch (error) {
    22
    console.error(error, "====error");
    23
    }
    24
    };

    Here's what the function does:#

  • Fetches your wallet account using the Portkey wallet provider.
  • Initializes the DAO smart contract if it hasn't been done already, updating the state and showing a success alert.
  • 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.

    Next, we'll write the Create Proposal function.

    Write Create Proposal Function#

    Let's write the Create Proposal function.

  • 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.
  • Find the comment Step D - Configure Proposal Form.
  • Replace the form variable with this code snippet:
  • src/CreateProposal.tsx

    1
    //Step D - Configure Proposal Form
    2
    const form = useForm<z.infer<typeof formSchema>>({
    3
    resolver: zodResolver(formSchema),
    4
    defaultValues: {
    5
    address: currentWalletAddress,
    6
    title: "",
    7
    description: "",
    8
    voteThreshold: 0,
    9
    },
    10
    });

    Here's what the function does:#

  • Initializes a new form variable with default values needed to create a proposal.
  • 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.

  • Scroll down to find the comment Step E - Write Create Proposal Logic.
  • Replace the onSubmit function with this code snippet:
  • src/CreateProposal.tsx

    1
    // Step E - Write Create Proposal Logic
    2
    function onSubmit(values: z.infer<typeof formSchema>) {
    3
    const proposalInput: IProposalInput = {
    4
    creator: currentWalletAddress,
    5
    title: values.title,
    6
    description: values.description,
    7
    voteThreshold: values.voteThreshold,
    8
    };
    9
    10
    setCreateProposalInput(proposalInput);
    11
    12
    const createNewProposal = async () => {
    13
    try {
    14
    await DAOContract?.callSendMethod(
    15
    "CreateProposal",
    16
    currentWalletAddress,
    17
    createProposalInput
    18
    );
    19
    20
    navigate("/");
    21
    alert("Successfully created proposal");
    22
    } catch (error) {
    23
    console.error(error);
    24
    }
    25
    };
    26
    27
    createNewProposal();
    28
    }

    Here's what the function does:#

  • Creates a new proposalInput variable with form fields: title , description , and vote threshold.
  • Invokes the CreateProposal function of the deployed smart contract, using the current wallet address and proposalInput.
  • 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.

  • Go to the src/HomeDAO.tsx file and scroll to the Step F - Write Vote Yes Logic comment.
  • Replace the voteYes function with this code snippet:
  • src/HomeDAO.tsx

    1
    const voteYes = async (index: number) => {
    2
    //Step F - Write Vote Yes Logic
    3
    try {
    4
    const accounts = await provider?.request({
    5
    method: MethodsBase.ACCOUNTS,
    6
    });
    7
    8
    if (!accounts) throw new Error("No accounts");
    9
    10
    const account = accounts?.tDVW?.[0];
    11
    12
    if (!account) throw new Error("No account");
    13
    14
    const createVoteInput: IVoteInput = {
    15
    voter: account,
    16
    proposalId: index,
    17
    vote: true,
    18
    };
    19
    20
    await DAOContract?.callSendMethod(
    21
    "VoteOnProposal",
    22
    account,
    23
    createVoteInput
    24
    );
    25
    alert("Voted on Proposal");
    26
    setHasVoted(true);
    27
    } catch (error) {
    28
    console.error(error, "=====error");
    29
    }
    30
    };

    Here's what the function does:#

  • Takes an index parameter, representing the proposal ID to vote on.
  • Fetches the wallet address using the Portkey provider.
  • Creates a createVoteInput parameter with the voter's wallet address, proposal ID, and a true value for a Yes vote..
  • Calls the VoteOnProposal function from the smart contract.
  • 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

    1
    useEffect(() => {
    2
    // Step G - Use Effect to Fetch Proposals
    3
    const fetchProposals = async () => {
    4
    try {
    5
    const accounts = await provider?.request({
    6
    method: MethodsBase.ACCOUNTS,
    7
    });
    8
    9
    if (!accounts) throw new Error("No accounts");
    10
    11
    const account = accounts?.tDVW?.[0];
    12
    13
    if (!account) throw new Error("No account");
    14
    15
    const proposalResponse = await DAOContract?.callViewMethod<IProposals>(
    16
    "GetAllProposals",
    17
    ""
    18
    );
    19
    20
    setProposals(proposalResponse?.data);
    21
    alert("Fetched Proposals");
    22
    } catch (error) {
    23
    console.error(error);
    24
    }
    25
    };
    26
    27
    fetchProposals();
    28
    }, [DAOContract, hasVoted, isConnected, joinedDAO]);

    Here's what the function does:#

  • Defines the fetchProposals function that fetches the wallet address.
  • Calls the GetAllProposals function from the smart contract, returning a list of proposals.
  • 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

    1
    npm run dev
  • You should observe the following as shown below.
  • Upon clicking on the localhost URL, you should be directed to the StackUpDAO landing page as shown below.
  • 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:
  • Click the link to open the Voting dApp in the browser.
  • Create Portkey Wallet#

  • Download the Chrome extension for Portkey from https://chromewebstore.google.com/detail/portkey-wallet/iglbgmakmggfkoidiagnhknlndljlolb.
  • Once you have downloaded the extension, you should see the following on your browser as shown below.
  • Click on Get Start and you should see the following interface as shown below.
  • Sign up

  • Switch to aelf Testnet network by selecting it:
  • 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.
  • With that, you have successfully created your very first Portkey wallet within seconds. How easy was that?

  • Next, click on β€˜Open Portkey’ and you should now observe the following as shown below.
  • 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.

  • Proposal #1 as defined in 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.
  • 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.

  • 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.
  • Proceed to fill in the following fields under the Create Proposal form:
  • click on "Submit" and you should observe the following as shown below.
  • 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.
  • πŸŽ‰ Congratulations Learners! You have successfully built your Voting dApp and this is no mean feat.

    Edited on: 12 July 2024 06:01:57 GMT+0