Create Publication

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

Tutorial Thumbnail
Intermediate · 1 hour

Building and Deploying a Decentralised Voting System with PyTeal and React

The business value of this tutorial is to teach how to write a smart contract code with PyTeal and Deploy using the Algorand JavaScript SDK and React on the Frontend to interact with the application.

PyTeal is a python library for generating TEAL programs that provide a convenient and familiar syntax.TEAL is a Transaction Execution Approval Language used for writing Smart Contract that will be deployed on the Algorand blockchain. TEAL allows writing of Stateful Smart Contract and Smart Signatures.

Interacting with a smart contract is one of the requirements for every smart contract. This tutorial will take you through a step-by-step guide from creating the smart contract with PyTeal to deploying it with JavaScript SDK and interacting with the Contract using React and signing the contract using the AlgoSigner chrome extension.

Resources

Requirements

Background

PyTeal is a python library for generating TEAL programs. PyTeal is used for writing smart contracts on the Algorand blockchain. The TEAL code will be deployed using the Algorand JavaScript SDK.

To interact with the smart contract the JavaScript SDK will be used to compile and deploy the PyTeal smart contract code. For a more user-friendly interaction with the smart contract React and AlgoSigner will be used.

AlgoSigner is a web chrome extension used for managing Algorand wallet and for the signing of transactions.

The goal of the voting smart contract is to enable voters to vote for their candidate of choice from a list of candidates. The votes will be stored globally on the Algorand blockchain.

The voting system allows accounts to register and vote for arbitrary choices. Here a choice is any byte slice and anyone is allowed to register to vote.

This tutorial has a configurable registration period defined by the global state Registration Begin and Registration End which restrict when accounts can register to vote. There is also a separate configurable voting period defined by the global state Voting Begin and Voting End which restrict when voting can take place. But for the purpose of testing/demo the registration and voting period will be left open.

An account must register in order to vote. Accounts cannot vote more than once. The results are visible in the global state of the application, and the winner is the candidate with the highest number of votes.

EditorImages/2022/03/25 02:09/Screenshot_2022-03-25_at_03.01.53.png

Fig 0-1 App UI

Steps

1. Project Setup and Installations

This section will cover the required installations for the smart contract and the frontend installations.
Install the required dependencies for the project.

PyTeal Smart Contract Installations

Setup your Python virtual environment. The following commands will activate the virtual environment and then install all dependencies, including PyTeal. Setup Python environment (one time).

python3 -m venv venv
Activate venv. Replace bin with Scripts on Windows.

. venv/bin/activate

EditorImages/2022/03/10 10:13/Screenshot_2022-03-02_at_19.57.55.png
Fig 1-1 Pyteal installation

Now install all requirements.
pip3 install -r requirements.txt

Run the contract.py file. This will compile the PyTeal code into a file.
python3 contract.py

Frontend Installations

If you don’t have node installed. You can install it here. After installation, you can confirm it by running this command on your terminal.

EditorImages/2022/03/10 01:02/Screenshot_2021-11-16_at_05.01.37.png
Fig 1-2 Confirm node is installed

npm install -g create-react-app

Install styled component. This will be used for css.
npm install --save styled-components

This will install react globally on your machine. The next thing to do is to create react app by running this command create-react-app pyteal-dapp pyteal dapp is the name of the react app, you can give it any name.

After creating the react app run npm start this will open up the boilerplate code.You should see something like this.
EditorImages/2022/03/10 02:02/Screenshot_2021-11-16_at_04.46.28.png
Fig 1-3 Confirm node is installed

Initialize project
npm init

Install Algorand JavaScript sdk
npm install algosdk

EditorImages/2022/03/10 10:15/Screenshot_2022-03-09_at_01.45.20.png
Fig 1-4 Algorand JavaScript SDK Installation

2. PyTeal Voting Smart Contract

PyTeal contracts are written in Python using any editor of choice, and then use PyTeal’s compileProgram method to produce the TEAL code. The TEAL source can be compiled into bytecode and deployed to the blockchain.

The PyTeal smart contract consists of two programs. These are called the approval program and the clear programs. In PyTeal both of these programs are generally created in the same Python file.

The ApprovalProgram is responsible for processing all application calls to the contract, with the exception of the clear call. This program is responsible for implementing most of the logic of an application. Like smart signatures, this program will succeed only if one nonzero value is left on the stack upon program completion or the return opcode is called with a positive value on the top of the stack.

The ClearStateProgram is used to handle accounts using the clear call to remove the smart contract from their balance record. This program will pass or fail the same way the ApprovalProgram does.

To create the PyTeal code create a contract.py file.

Handle On create

This code handles the creation of the contract. This sets up the global state arguments for the contract. The global state for the Creator is set to the sender of the transactionTxn.sender(). The Assert checks to ensure that on creation of the contract the application args is equal to 4. Anything less than that the Approval Program will fail. TheRegBegin , RegEnd, voteBegin, voteEnd are set to the global state.

on_creation = Seq(
        [
            App.globalPut(Bytes("Creator"), Txn.sender()),
            Assert(Txn.application_args.length() == Int(4)),
            App.globalPut(Bytes("RegBegin"), Btoi(Txn.application_args[0])),
            App.globalPut(Bytes("RegEnd"), Btoi(Txn.application_args[1])),
            App.globalPut(Bytes("VoteBegin"), Btoi(Txn.application_args[2])),
            App.globalPut(Bytes("VoteEnd"), Btoi(Txn.application_args[3])),
            Return(Int(1)),
        ]
    )

Handle Optin

This handles optin of the application. This compares the current round with the value set for RegBegin and RegEnd

   on_register = Return(
        And(
            Global.round() >= App.globalGet(Bytes("RegBegin")),
            Global.round() <= App.globalGet(Bytes("RegEnd")),
        )
    )

Handle Close Out

This will handle the close-out of the application. The code below gets the vote of the sender and puts the value of the user’s vote on the global state. It also checks if the current round is less than the end time of the vote. And if the user already voted the application call will fail.

Note

To close out the application the user needs to first optin.

 get_vote_of_sender = App.localGetEx(Int(0), App.id(), Bytes("voted"))

    on_closeout = Seq(
        [
            get_vote_of_sender,
            If(
                And(
                    Global.round() <= App.globalGet(Bytes("VoteEnd")),
                    get_vote_of_sender.hasValue(),
                ),
                App.globalPut(
                    get_vote_of_sender.value(),
                    App.globalGet(get_vote_of_sender.value()) - Int(1),
                ),
            ),
            Return(Int(1)),
        ]
    )

Handle Noop

This is where most of the logic of the code happens. This gets the choice of the user from the global state.
It also Assert to check the voting periods. It checks if the user has already voted. If the user has already voted it will return Return(Int(0)) otherwise Return(Int(1))

 choice = Txn.application_args[1]
    choice_tally = App.globalGet(choice)
    on_vote = Seq(
        [
            Assert(
                And(
                    Global.round() >= App.globalGet(Bytes("VoteBegin")),
                    Global.round() <= App.globalGet(Bytes("VoteEnd")),
                )
            ),
            get_vote_of_sender,
            If(get_vote_of_sender.hasValue(), Return(Int(0))),
            App.globalPut(choice, choice_tally + Int(1)),
            App.localPut(Int(0), Bytes("voted"), choice),
            Return(Int(1)),
        ]
    )

Check the creator

The is_creator value is passed in the OnComplete.DeleteApplication and OnComplete.UpdateApplication. This ensures that only the creator can delete and update the application.

is_creator = Txn.sender() == App.globalGet(Bytes("Creator"))

OnComplete

The onComplete is used to check all conditions. This section is a very important aspect of the approval program.

  program = Cond(
        [Txn.application_id() == Int(0), on_creation],
        [Txn.on_completion() == OnComplete.DeleteApplication, Return(is_creator)],
        [Txn.on_completion() == OnComplete.UpdateApplication, Return(is_creator)],
        [Txn.on_completion() == OnComplete.CloseOut, on_closeout],
        [Txn.on_completion() == OnComplete.OptIn, on_register],
        [Txn.application_args[0] == Bytes("vote"), on_vote],
    )

The Clear State Program

The clear state program handles clearing of the local state of the user. The clear state program can also fail like the approval program.

def clear_state_program():
    get_vote_of_sender = App.localGetEx(Int(0), App.id(), Bytes("voted"))
    program = Seq(
        [
            get_vote_of_sender,
            If(
                And(
                    Global.round() <= App.globalGet(Bytes("VoteEnd")),
                    get_vote_of_sender.hasValue(),
                ),
                App.globalPut(
                    get_vote_of_sender.value(),
                    App.globalGet(get_vote_of_sender.value()) - Int(1),
                ),
            ),
            Return(Int(1)),
        ]
    )

    return program

3. Full PyTeal Voting Smart Contract

// src/contracts/contract.py

from pyteal import *


def approval_program():
    on_creation = Seq(
        [
            App.globalPut(Bytes("Creator"), Txn.sender()),
            Assert(Txn.application_args.length() == Int(4)),
            App.globalPut(Bytes("RegBegin"), Btoi(Txn.application_args[0])),
            App.globalPut(Bytes("RegEnd"), Btoi(Txn.application_args[1])),
            App.globalPut(Bytes("VoteBegin"), Btoi(Txn.application_args[2])),
            App.globalPut(Bytes("VoteEnd"), Btoi(Txn.application_args[3])),
            Return(Int(1)),
        ]
    )

    is_creator = Txn.sender() == App.globalGet(Bytes("Creator"))

    get_vote_of_sender = App.localGetEx(Int(0), App.id(), Bytes("voted"))

    on_closeout = Seq(
        [
            get_vote_of_sender,
            If(
                And(
                    Global.round() <= App.globalGet(Bytes("VoteEnd")),
                    get_vote_of_sender.hasValue(),
                ),
                App.globalPut(
                    get_vote_of_sender.value(),
                    App.globalGet(get_vote_of_sender.value()) - Int(1),
                ),
            ),
            Return(Int(1)),
        ]
    )

    on_register = Return(
        And(
            Global.round() >= App.globalGet(Bytes("RegBegin")),
            Global.round() <= App.globalGet(Bytes("RegEnd")),
        )
    )

    choice = Txn.application_args[1]
    choice_tally = App.globalGet(choice)
    on_vote = Seq(
        [
            Assert(
                And(
                    Global.round() >= App.globalGet(Bytes("VoteBegin")),
                    Global.round() <= App.globalGet(Bytes("VoteEnd")),
                )
            ),
            get_vote_of_sender,
            If(get_vote_of_sender.hasValue(), Return(Int(0))),
            App.globalPut(choice, choice_tally + Int(1)),
            App.localPut(Int(0), Bytes("voted"), choice),
            Return(Int(1)),
        ]
    )

    program = Cond(
        [Txn.application_id() == Int(0), on_creation],
        [Txn.on_completion() == OnComplete.DeleteApplication, Return(is_creator)],
        [Txn.on_completion() == OnComplete.UpdateApplication, Return(is_creator)],
        [Txn.on_completion() == OnComplete.CloseOut, on_closeout],
        [Txn.on_completion() == OnComplete.OptIn, on_register],
        [Txn.application_args[0] == Bytes("vote"), on_vote],
    )

    return program


def clear_state_program():
    get_vote_of_sender = App.localGetEx(Int(0), App.id(), Bytes("voted"))
    program = Seq(
        [
            get_vote_of_sender,
            If(
                And(
                    Global.round() <= App.globalGet(Bytes("VoteEnd")),
                    get_vote_of_sender.hasValue(),
                ),
                App.globalPut(
                    get_vote_of_sender.value(),
                    App.globalGet(get_vote_of_sender.value()) - Int(1),
                ),
            ),
            Return(Int(1)),
        ]
    )

    return program


if __name__ == "__main__":
    with open("vote_approval.teal", "w") as f:
        compiled = compileTeal(approval_program(), mode=Mode.Application, version=2)
        f.write(compiled)

    with open("vote_clear_state.teal", "w") as f:
        compiled = compileTeal(clear_state_program(), mode=Mode.Application, version=2)
        f.write(compiled)

4. Compiled PyTeal Code to TEAL Opcode

Approval Program

// src/contract/vote_approval.teal

#pragma version 5
txn ApplicationID
int 0
==
bnz main_l16
txn OnCompletion
int DeleteApplication
==
bnz main_l15
txn OnCompletion
int UpdateApplication
==
bnz main_l14
txn OnCompletion
int CloseOut
==
bnz main_l11
txn OnCompletion
int OptIn
==
bnz main_l10
txna ApplicationArgs 0
byte "vote"
==
bnz main_l7
err
main_l7:
global Round
byte "VoteBegin"
app_global_get
<=
global Round
byte "VoteEnd"
app_global_get
<=
&&
assert
int 0
global CurrentApplicationID
byte "voted"
app_local_get_ex
store 1
store 0
load 1
bnz main_l9
txna ApplicationArgs 1
txna ApplicationArgs 1
app_global_get
int 1
+
app_global_put
int 0
byte "voted"
txna ApplicationArgs 1
app_local_put
int 1
return
main_l9:
int 0
return
main_l10:
global Round
byte "RegBegin"
app_global_get
<=
global Round
byte "RegEnd"
app_global_get
<=
&&
return
main_l11:
int 0
global CurrentApplicationID
byte "voted"
app_local_get_ex
store 1
store 0
global Round
byte "VoteEnd"
app_global_get
<=
load 1
&&
bnz main_l13
main_l12:
int 1
return
main_l13:
load 0
load 0
app_global_get
int 1
-
app_global_put
b main_l12
main_l14:
txn Sender
byte "Creator"
app_global_get
==
return
main_l15:
txn Sender
byte "Creator"
app_global_get
==
return
main_l16:
byte "Creator"
txn Sender
app_global_put
txn NumAppArgs
int 4
==
assert
byte "RegBegin"
txna ApplicationArgs 0
btoi
app_global_put
byte "RegEnd"
txna ApplicationArgs 1
btoi
app_global_put
byte "VoteBegin"
txna ApplicationArgs 2
btoi
app_global_put
byte "VoteEnd"
txna ApplicationArgs 3
btoi
app_global_put
int 1
return

Clear state program

// src/contract/vote_clear_state.teal

#pragma version 5
int 0
global CurrentApplicationID
byte "voted"
app_local_get_ex
store 1
store 0
global Round
byte "VoteEnd"
app_global_get
<=
load 1
&&
bz main_l2
load 0
load 0
app_global_get
int 1
-
app_global_put
main_l2:
int 1
return

Note

The above TEAL code can be compiled and deployed using any of the Algorand SDK. But for this tutorial, the JavaScript SDK will be used to deploy the application.

5. Full Deployment Code with JavaScript SDK

Calls to smart contracts are implemented using ApplicationCall transactions. These transaction types are as follows:

  • NoOp - Generic application calls to execute the ApprovalProgram. In this tutorial, with the NoOp transaction we are executing the logic of the Smart Contract that handles the voting. We provide two additional arguments when executing this transaction:
    - vote - this argument represents that we are executing a vote in the smart contract. It is just a plain string.
    - choice - the second argument represents the voting choice by the address that is executing the current vote in the Smart Contract.
  • OptIn - Accounts use this transaction to begin participating in a smart contract. Participation enables local storage usage. Using this transaction we enable to user to interact with the Voting Smart Contract.
  • GlobalState - This put and get the global state of the application.
  • LocalState - This put and get items from the local state.
  • DeleteApplication - Transaction to delete the application.
  • UpdateApplication - Transaction to update TEAL Programs for a contract.
  • CloseOut - Accounts use this transaction to close out their participation in the contract. This call can fail based on the TEAL logic, preventing the account from removing the contract from its balance record. Using this transaction we are enabling the user to remove the vote if the voting period is still active.
  • ClearState - Similar to CloseOut, but the transaction will always clear a contract from the account’s balance record whether the program succeeds or fails.

The ClearStateProgram handles the ClearState transaction and the ApprovalProgram handles all other ApplicationCall transactions.

For detailed explanation on Smart contracts and their lifecycles, check here

Account Creation

For faster account creation, you can use the code below. You can also create an account using myalgo wallet

Note

Do not expose your mnemonic phrase/ private key. These are only publicly available for testing purposes.

// deployment.js

// get accounts from mnemonic
  const creatorMnemonic = "CREATOR MNEMONIC"
  const userMnemonic = "USER MNEMONIC"
  const creatorAccount = algosdk.mnemonicToSecretKey(creatorMnemonic)
  const userAccout =  algosdk.mnemonicToSecretKey(userMnemonic)
  const creatorSecret = creatorAccount.sk
  const creatorAddress = creatorAccount.addr
  const sender = userAccout.addr

  //Generate Account
  const account = algosdk.generateAccount()
  const secrekey = account.sk
  const mnemonic = algosdk.secretKeyToMnemonic(secrekey)
  console.log("mnemonic " + mnemonic )
  console.log("address " + account.addr )

Note

The creator or any account on the Algorand blockchain can only create 10 apps per account. And to create an app the account needs to be funded to meet the minimum balance requirements. To fund your test account simply go to the Algorand Dispenser.

To test the individual functions of the code on the console, run node deployment.js

// src/contract/deployment.js

  const dotenv = require('dotenv')
  const fs = require('fs')
  const algosdk = require('algosdk');
  dotenv.config()

//SMART CONTRACT DEPLOYMENT
  // declare application state storage (immutable)
  const localInts = 0;
  const localBytes = 1;
  const globalInts = 24; //# 4 for setup + 20 for choices. Use a larger number for more choices.
  const globalBytes = 1;

  // get accounts from mnemonic
  const creatorMnemonic = "CREATOR MNEMONIC"
  const userMnemonic = "USER MNEMONIC"
  const creatorAccount = algosdk.mnemonicToSecretKey(creatorMnemonic)
  const userAccout =  algosdk.mnemonicToSecretKey(userMnemonic)
  const creatorAddress = creatorAccount.addr
  const sender = userAccout.addr

  //Generate Account
  const account = algosdk.generateAccount()
  const secrekey = account.sk
  const mnemonic = algosdk.secretKeyToMnemonic(secrekey)
  console.log("mnemonic " + mnemonic )
  console.log("address " + account.addr )

  console.log()
  // Connect your client
  const algodToken = "API-KEY";
  const baseServer = 'https://testnet-algorand.api.purestake.io/ps2/';
  const port = "";
  const headers ={"X-API-Key": "API-KEY"}  

  console.log(process.env) 
  let client = new algosdk.Algodv2(algodToken, baseServer, port, headers)

  // Read Teal File
  let approvalProgram = ''
  let clear_state_program = ''

  try {
    approvalProgram = fs.readFileSync('../contract/vote_approval.teal', 'utf8')
    clear_state_program = fs.readFileSync('../contract/vote_clear_state.teal', 'utf8')
    // console.log(approvalProgram)
    // console.log(clear_state_program)
  } catch (err) {
    console.error(err)
  }

  // Compile Program
  const compileProgram = async (client, programSource) => {
  let encoder = new TextEncoder();
  let programBytes = encoder.encode(programSource);
  let compileResponse = await client.compile(programBytes).do();
  let compiledBytes = new Uint8Array(Buffer.from(compileResponse.result, "base64"));
  // console.log(compileResponse)
  return compiledBytes;
}

// convert 64 bit integer i to byte string
const intToBytes = (integer) => {
  return integer.toString()
}

//CREATE APP
// create unsigned transaction
const createApp = async (sender, 
  approvalProgram, clearProgram, 
  localInts, localBytes, globalInts, globalBytes, app_args) => {
    try{
      const onComplete = algosdk.OnApplicationComplete.NoOpOC;

      let params = await client.getTransactionParams().do()
      params.fee = 1000;
      params.flatFee = true;

      console.log("suggestedparams" + params)

        let txn = algosdk.makeApplicationCreateTxn(sender, params, onComplete, 
          approvalProgram, clearProgram, 
          localInts, localBytes, globalInts, globalBytes, app_args);
        let txId = txn.txID().toString();
        // Sign the transaction
        let signedTxn = txn.signTxn(creatorAccount.sk);
        console.log("Signed transaction with txID: %s", txId);

        // Submit the transaction
        await client.sendRawTransaction(signedTxn).do()                           
            // Wait for transaction to be confirmed
           let confirmedTxn = await algosdk.waitForConfirmation(client, txId, 4);
            console.log("confirmed" + confirmedTxn)

            //Get the completed Transaction
            console.log("Transaction " + txId + " confirmed in round " + confirmedTxn["confirmed-round"]);
            // display results
            let transactionResponse = await client.pendingTransactionInformation(txId).do()
            let appId = transactionResponse['application-index'];
            console.log("Created new app-id: ",appId);
            return appId
      }catch(err){
      console.log(err)
    }
}

//OPTIN
// create unsigned transaction
const Optin = async (sender, index) => {
  try{
    let params = await client.getTransactionParams().do()
    params.fee = 1000;
    params.flatFee = true;

    let txn = algosdk.makeApplicationOptInTxn(sender, params, index);
    let txId = txn.txID().toString();
    // sign, send, await
    // Sign the transaction
    let signedTxn = txn.signTxn(userAccout.sk);
    console.log("Signed transaction with txID: %s", txId);

    // Submit the transaction
    await client.sendRawTransaction(signedTxn).do()                           
        // Wait for transaction to be confirmed
       const confirmedTxn = await algosdk.waitForConfirmation(client, txId, 4);
        console.log("confirmed" + confirmedTxn)

        //Get the completed Transaction
        console.log("Transaction " + txId + " confirmed in round " + confirmedTxn["confirmed-round"]);
        // display results
    // display results
    let transactionResponse = await client.pendingTransactionInformation(txId).do();
    console.log("Opted-in to app-id:",transactionResponse['txn']['txn']['apid'])
  }catch(err){
    console.log(err)
  }
}


//  CALL(NOOP)
// call application with arguments
const noop = async (sender, index)  => {
  try{
  let vote = "vote"
  // let choice = localStorage.getItem("candidate")
  const appArgs = []
  appArgs.push(
    new Uint8Array(Buffer.from(vote)),
    new Uint8Array(Buffer.from("choice")),
   )
  let params = await client.getTransactionParams().do()
    params.fee = 1000;
    params.flatFee = true;

  // create unsigned transaction
  let txn = algosdk.makeApplicationNoOpTxn(sender, params, index, appArgs)

    let txId = txn.txID().toString();
    // Sign the transaction
    let signedTxn = txn.signTxn(userAccout.sk);
    console.log("Signed transaction with txID: %s", txId);

    // Submit the transaction
    await client.sendRawTransaction(signedTxn).do()                           
        // Wait for transaction to be confirmed
       const confirmedTxn = await algosdk.waitForConfirmation(client, txId, 4);
        console.log("confirmed" + confirmedTxn)

        //Get the completed Transaction
        console.log("Transaction " + txId + " confirmed in round " + confirmedTxn["confirmed-round"]);

  // display results
  let transactionResponse = await client.pendingTransactionInformation(txId).do();
  console.log("Called app-id:",transactionResponse['txn']['txn']['apid'])
  if (transactionResponse['global-state-delta'] !== undefined ) {
      console.log("Global State updated:",transactionResponse['global-state-delta']);
  }
  if (transactionResponse['local-state-delta'] !== undefined ) {
      console.log("Local State updated:",transactionResponse['local-state-delta']);
  }
  }catch(err){
    console.log(err)
  }
}

//READ STATE
// read local state of application from user account
const readLocalState = async (index) => {
  try{
    let accountInfoResponse = await client.accountInformation(userAccout.addr).do();
    let localState = accountInfoResponse['apps-local-state']
    return localState.map((item)=> {
      if(item['id'] == index){
        console.log("User's local state:" + item.id);
        let localStateItem = accountInfoResponse['apps-local-state'][item]['key-value']
        localStateItem.map((local) =>{
          console.log(local)
          return local
        })
      }
      return item
    })
  }catch(err){
    console.log(err)
  }
}


// read global state of application
const readGlobalState = async (index) => {
  try{
    let applicationInfoResponse = await client.getApplicationByID(index).do();
    let globalState = applicationInfoResponse['params']['global-state']
    return globalState.map((state) =>{
      return state
    })
  }catch(err){
    console.log(err)
  }
}

//UPDATE
// create unsigned transaction
const update = async (sender, index, approvalProgram, clearProgram) => {
  try{
    let params = await client.getTransactionParams().do()
    params.fee = 1000;
    params.flatFee = true;

  let txn = algosdk.makeApplicationUpdateTxn(sender, params, index, approvalProgram, clearProgram);
// sign, send, await
  let txId = txn.txID().toString();
  // Sign the transaction
  let signedTxn = txn.signTxn(creatorAccount.sk);
  console.log("Signed transaction with txID: %s", txId);

  // Submit the transaction
  await client.sendRawTransaction(signedTxn).do()                           
      // Wait for transaction to be confirmed
     const confirmedTxn = await algosdk.waitForConfirmation(client, txId, 4);
      console.log("confirmed" + confirmedTxn)

      //Get the completed Transaction
      console.log("Transaction " + txId + " confirmed in round " + confirmedTxn["confirmed-round"]);

  // display results
  let transactionResponse = await client.pendingTransactionInformation(txId).do();
  let appId = transactionResponse['txn']['txn'].apid;
  console.log("Updated app-id: ",appId);
  }catch(err){
    console.log(err)
  }
}


// CLOSE OUT
// create unsigned transaction
const  closeOut = async (sender, index) => {
  try{
    let params = await client.getTransactionParams().do()
    params.fee = 1000;
    params.flatFee = true;
    let txn = algosdk.makeApplicationCloseOutTxn(sender, params, index)
  // sign, send, await
    let txId = txn.txID().toString();
      // Sign the transaction
      let signedTxn = txn.signTxn(userAccout.sk);
      console.log("Signed transaction with txID: %s", txId);

      // Submit the transaction
      await client.sendRawTransaction(signedTxn).do()                           
          // Wait for transaction to be confirmed
         const confirmedTxn = await algosdk.waitForConfirmation(client, txId, 4);
          console.log("confirmed" + confirmedTxn)

          //Get the completed Transaction
          console.log("Transaction " + txId + " confirmed in round " + confirmedTxn["confirmed-round"]);

      // display results
      let transactionResponse = await client.pendingTransactionInformation(txId).do();
      console.log("Closed out from app-id:",transactionResponse['txn']['txn']['apid'])
  }catch(err){
    console.log(err)
  }
}


//DELETE
// create unsigned transaction
const deleteApp = async (sender, index) => {
  try{
    let params = await client.getTransactionParams().do()
    params.fee = 1000;
    params.flatFee = true;
    let txn = algosdk.makeApplicationDeleteTxn(sender, params, index);
    // sign, send, await
    let txId = txn.txID().toString();
      // Sign the transaction
      let signedTxn = txn.signTxn(creatorAccount.sk);
      console.log("Signed transaction with txID: %s", txId);

      // Submit the transaction
      await client.sendRawTransaction(signedTxn).do()                           
          // Wait for transaction to be confirmed
         const confirmedTxn = await algosdk.waitForConfirmation(client, txId, 4);
          console.log("confirmed" + confirmedTxn)

          //Get the completed Transaction
          console.log("Transaction " + txId + " confirmed in round " + confirmedTxn["confirmed-round"]);

    // display results
    let transactionResponse = await client.pendingTransactionInformation(txId).do();
    let appId = transactionResponse['txn']['txn'].apid;
    console.log("Deleted app-id: ",appId);
  }catch(err){
    console.log(err)
  }
}


// CLEAR STATE
// create unsigned transaction
const clearState = async (sender, index) => {
  try{
    let params = await client.getTransactionParams().do()
    params.fee = 1000;
    params.flatFee = true;
  let txn = algosdk.makeApplicationClearStateTxn(sender, params, index);
  let txId = txn.txID().toString();
  // sign, send, await
  let signedTxn = txn.signTxn(userAccout.sk);
    console.log("Signed transaction with txID: %s", txId);

    // Submit the transaction
    await client.sendRawTransaction(signedTxn).do()                           
        // Wait for transaction to be confirmed
       const confirmedTxn = await algosdk.waitForConfirmation(client, txId, 4);
        console.log("confirmed" + confirmedTxn)

        //Get the completed Transaction
        console.log("Transaction " + txId + " confirmed in round " + confirmedTxn["confirmed-round"]);
  // display results
  let transactionResponse = await client.pendingTransactionInformation(txId).do();
  let appId = transactionResponse['txn']['txn'].apid;
  console.log("Cleared local state for app-id: ",appId);
  }catch(err){
    console.log(err)
  }
}

const main = async () => {

const approval_program = await compileProgram(client, approvalProgram)
const clear_program = await  compileProgram(client,clear_state_program )

// configure registration and voting period
let status = await client.status().do()
console.log(status)
let RegBegin =  status['last-round'] + 10
let RegEnd = RegBegin + 10
let VoteBegin = RegEnd + 10
let VoteEnd = VoteBegin + 10

// const regTime = `Registration rounds: ${RegBegin} to ${RegEnd}`
// const voteTime = `Vote rounds: ${VoteBegin} to ${VoteEnd}`
// localStorage.setItem('start', regTime)
// localStorage.setItem('end',voteTime )

console.log(`Registration rounds: ${RegBegin} to ${RegEnd}`)
console.log(`Vote rounds: ${VoteBegin} to ${VoteEnd}`)


// create list of bytes for app args
let appArgs = [];

console.log(appArgs.push(
  new Uint8Array(Buffer.from(intToBytes(RegBegin))),
  new Uint8Array(Buffer.from(intToBytes(RegEnd))),
  new Uint8Array(Buffer.from(intToBytes(VoteBegin))),
  new Uint8Array(Buffer.from(intToBytes(VoteEnd))),
 ))

// create new application
const appId =  await createApp(creatorAddress, approval_program, clear_program , localInts, localBytes, globalInts, globalBytes, appArgs)
console.log(appId)

const globalState = await readGlobalState(appId)
console.log(globalState)

await Optin(userAccout.addr, appId)

noop(sender, appId)

//  read localstate of application
const localState = await  readLocalState(client, sender,  appId)
console.log(localState)

//  read globalstate of application
const gloablState = await readGlobalState(appId)
console.log(gloablState)

//Converting to base64

const args = [
  btoa("RegBegin"),
  btoa("RegEnd"),
  btoa("VoteBegin"),
  btoa("VoteEnd"),
  btoa("Creator"),
  // btoa("choiceA"),
]

let filteredItems = []
gloablState.forEach(item => {
  if (!args.includes(item.key)) {
    filteredItems.push(item)
  }
})

// const maxNum = Math.max.apply(Math, filteredItems.map(function(o) { return o.value.uint; }))
// console.log(maxNum)
// let maxVote = filteredItems.reduce((max, item) => max.value.uint > item.value.uint ? max.key : item);
// console.log(atob(maxVote))

await closeOut(sender, appId)
await deleteApp(creatorAddress, appId)
// await clearState(sender, appId)
}
main()

6. Testing of the Code on Console

EditorImages/2022/03/10 08:55/Screenshot_2022-03-10_at_09.10.20.png
Fig 6-1 Create App

EditorImages/2022/03/10 08:59/Screenshot_2022-03-10_at_09.24.58.png
Fig 6-2 Successful Optin

This message comes up when the user has already opted in.
EditorImages/2022/03/10 09:01/Screenshot_2022-03-10_at_09.25.53.png
Fig 6-3 Error message for unsuccessful Optin

EditorImages/2022/03/10 09:04/Screenshot_2022-03-10_at_09.27.37.png
Fig 6-4 Global state before noops

EditorImages/2022/03/10 09:15/Screenshot_2022-03-10_at_09.29.10.png
Fig 6-5 Noops

EditorImages/2022/03/10 09:17/Screenshot_2022-03-10_at_09.29.30.png
Fig 6-6 Global State

EditorImages/2022/03/10 09:19/Screenshot_2022-03-10_at_09.34.03.png
Fig 6-7 Local State

EditorImages/2022/03/10 09:20/Screenshot_2022-03-10_at_09.35.52.png
Fig 6-8 Clear State

EditorImages/2022/03/10 09:21/Screenshot_2022-03-10_at_09.39.11.png
Fig 6-9 Delete App

EditorImages/2022/03/10 09:23/Screenshot_2022-03-10_at_09.45.12.png
Fig 6-10 Algoexplorer Transaction Detail I
After Deleting the App it shows 2 revisions on the explorer

EditorImages/2022/03/10 09:26/Screenshot_2022-03-10_at_09.45.27.png
Fig 6-11 Algoexplorer Transaction Detail II

Common Errors

EditorImages/2022/03/10 09:31/Screenshot_2022-03-10_at_09.36.50.png
Fig 6-12 Clear State Error
This happens when the app has already been cleared.

EditorImages/2022/03/10 09:34/Screenshot_2022-03-10_at_09.38.03.png
Fig 6-13 Optin required

EditorImages/2022/03/10 09:34/Screenshot_2022-03-10_at_09.41.13.png
Fig 6-13 Signing with the Wrong Key

EditorImages/2022/03/10 09:36/Screenshot_2022-03-10_at_09.42.14.png
Fig 6-14App Already Deleted or Does not Exist

7. React Front end Interaction with the Smart Contract

Connect Wallet

EditorImages/2022/03/10 10:20/Screenshot_2022-03-09_at_19.18.26.png
Fig 7-1 Connect Wallet with AlgoSigner

EditorImages/2022/03/10 10:20/Screenshot_2022-03-09_at_03.43.10.png
Fig 7-2 Connect Wallet

Before the user can register he needs to first connect wallet using the AlgoSigner chrome extension

// src/components/headers.js

const connectAlgoSigner = async () => {
    let resp = await AlgoSigner.connect()
        console.log(resp)
        getUserAccount()
        // if()
  }

  const getUserAccount = async () =>{
    userAccount.current =  await AlgoSigner.accounts({
         ledger: 'TestNet'
       })
 // console.log(userAccount.current[0]['address'])
 console.log(userAccount.current)

 }

Register

EditorImages/2022/03/10 10:26/Screenshot_2022-03-10_at_07.12.20.png
Fig 7-3 Register -Optin

This code get executed when the register button is clicked. This Optin function is called when the register button is clicked. If the users has already opted in the Optin will be unsuccessful. The minimum balance requirement has to be met too.

// src/components/headers.js

let client = new algosdk.Algodv2(CONSTANTS.algodToken, CONSTANTS.baseServer, CONSTANTS.port)

 //OPTIN
// create unsigned transaction
const Optin = async (sender, index) => {
  try{
    let params = await client.getTransactionParams().do()
    params.fee = 1000;
    params.flatFee = true;

    let txn = algosdk.makeApplicationOptInTxn(sender, params, index);
    // sign, send, await
    // Sign the transaction

    const txn_b64 = await AlgoSigner.encoding.msgpackToBase64(txn.toByte());

    let signedTxs  = await AlgoSigner.signTxn([{txn: txn_b64}])
     console.log(signedTxs)

     // Get the base64 encoded signed transaction and convert it to binary
   let binarySignedTx = await AlgoSigner.encoding.base64ToMsgpack(signedTxs[0].blob);

    // Send the transaction through the SDK client
   let txId = await client.sendRawTransaction(binarySignedTx).do();
       console.log(id)

    // Wait for transaction to be confirmed
    const confirmedTxn = await algosdk.waitForConfirmation(client, txId, 4);
     console.log("confirmed" + confirmedTxn)

    // Get the completed Transaction
    console.log("Transaction " + txId + " confirmed in round " + confirmedTxn["confirmed-round"]);
    // display results
     let transactionResponse = await client.pendingTransactionInformation(txId).do();
     console.log("Opted-in to app-id:",transactionResponse['txn']['txn']['apid'])
  }catch(err){
    console.log(err)
  }
}

Vote

When the vote button is clicked the Noop function is called. This function enables the user to make a choice and the choice is passed as an arg in the Noop function. if the user has already voted the call will be unsuccessful. The minimum balance requirement has to be met too.

EditorImages/2022/03/10 10:25/Screenshot_2022-03-10_at_07.09.26.png
Fig 7-4 Vote

EditorImages/2022/03/10 10:27/Screenshot_2022-03-10_at_07.13.00.png
Fig 7-5 Vote -Noop
// src/components/CandidateModal.js

let client = new algosdk.Algodv2(CONSTANTS.algodToken, CONSTANTS.baseServer, CONSTANTS.port)

  //  CALL(NOOP)
// call application with arguments
const noop = async (index, choice)  => {
  try{
    userAccount.current =   await AlgoSigner.accounts({
      ledger: 'TestNet'
    })
    const sender = userAccount.current[0]['address']
// console.log(userAccount.current[0]['address'])
console.log(userAccount.current)

  let vote = "vote"
  // let choice = localStorage.getItem("candidate")
  // console.log("choice is " + choice)
  const appArgs = []
  appArgs.push(
    new Uint8Array(Buffer.from(vote)),
    new Uint8Array(Buffer.from(choice)),
   )
  let params = await client.getTransactionParams().do()
    params.fee = 1000;
    params.flatFee = true;

  // create unsigned transaction
  let txn = algosdk.makeApplicationNoOpTxn(sender, params, index, appArgs)
    // Sign the transaction

    // Use the AlgoSigner encoding library to make the transactions base64
    const txn_b64 = await AlgoSigner.encoding.msgpackToBase64(txn.toByte());

    let signedTxs  = await AlgoSigner.signTxn([{txn: txn_b64}])
    console.log(signedTxs)

    // Get the base64 encoded signed transaction and convert it to binary
    let binarySignedTx = await AlgoSigner.encoding.base64ToMsgpack(signedTxs[0].blob);

    // Send the transaction through the SDK client
    let id = await client.sendRawTransaction(binarySignedTx).do();
      console.log(id)

       const confirmedTxn = await algosdk.waitForConfirmation(client, txId, 4);
        console.log("confirmed" + confirmedTxn)

        //Get the completed Transaction
        console.log("Transaction " + txId + " confirmed in round " + confirmedTxn["confirmed-round"]);

  // display results
  let transactionResponse = await client.pendingTransactionInformation(txId).do();
  console.log("Called app-id:",transactionResponse['txn']['txn']['apid'])
  if (transactionResponse['global-state-delta'] !== undefined ) {
      console.log("Global State updated:",transactionResponse['global-state-delta']);
  }
  if (transactionResponse['local-state-delta'] !== undefined ) {
      console.log("Local State updated:",transactionResponse['local-state-delta']);
  }
  }catch(err){
    console.log(err)
  }
}

Result

When the result button is clicked, it shows the result list of candidates that was voted for and their values. The user with the highest vote count is the winner. This reads the data on the Global State e to to display on the result modal. The Global state returns the list of candidates and their vote count.

EditorImages/2022/03/25 02:11/Screenshot_2022-03-25_at_03.05.55.png
Fig 7-6 Result

// src/components/ResultModal.js

 const client = new algosdk.Algodv2(CONSTANTS.algodToken, CONSTANTS.baseServer, CONSTANTS.port)
// read global state of application
  const readGlobalState = async (index) => {
    try{
      let applicationInfoResponse = await client.getApplicationByID(index).do();
      let globalState = applicationInfoResponse['params']['global-state']
      return globalState.map((state) =>{
        return state
      })
    }catch(err){
      console.log(err)
    }
  }
  const args = [
    btoa("RegBegin"),
    btoa("RegEnd"),
    btoa("VoteBegin"),
    btoa("VoteEnd"),
    btoa("Creator"),
  ]


var filteredItems = []
const fetchGlobalState = async () => {
  const gloablState = await readGlobalState(76645072)
  gloablState.forEach(item => {
  if (!args.includes(item.key)) {
    filteredItems.push(item)
  }
  console.log(filteredItems)
  //Code to get the winner with the highest vote count
  // let maxGame = filteredItems.reduce((max, item) => max.value.uint > item.value.uint ? max.key : item);
  // console.log(atob(maxGame))

  //Get all Candidates that was voted for
  // filteredItems.map((item) => item)
})}`

8. Next Steps

  • Create a DAO template.
  • Contract to Contract Interactions
  • More complex PyTeal contract
  • ABI interactions with the smart contract.

9. Further Study

These are important links to dive deeper into understanding how to build a Smart Contract on Algorand.

Warning

This tutorial is intended for learning purposes only. It does not cover error checking and other edge cases. Therefore, it should not be used as a production application