Chapter 17: Deploying a vote casting smart contract on a cross-chain setup and integrating it with a frontend-solidity

Chapter 17: Deploying a vote casting smart contract on a cross-chain setup and integrating it with a frontend-solidity

Deploying a vote casting smart settlement on a pass-chain setup and integrating it with a frontend includes more than one steps. Here’s the breakdown of what we will cowl:

  1. Smart Contract Development: A Solidity smart contract for voting.
  2. Cross-Chain Deployment: Using LayerZero, Wormhole, or Axelar for interoperability.
  3. Frontend Integration: Using React.js and Ethers.js to interact with the contract.

1. Smart Contract -Solidity

We’ll create a easy balloting contract in which customers can vote for applicants.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";

contract CrossChainVoting is Ownable {
    struct Candidate {
        string name;
        uint256 votes;
    }

    mapping(uint256 => Candidate) public candidates;
    mapping(address => bool) public hasVoted;
    uint256 public candidateCount;

    event Voted(address indexed voter, uint256 indexed candidateId);

    constructor() {
        _addCandidate("Alice");
        _addCandidate("Bob");
    }

    function _addCandidate(string memory _name) internal {
        candidates[candidateCount] = Candidate(_name, 0);
        candidateCount++;
    }

    function vote(uint256 _candidateId) external {
        require(!hasVoted[msg.sender], "You have already voted");
        require(_candidateId < candidateCount, "Invalid candidate");

        hasVoted[msg.sender] = true;
        candidates[_candidateId].votes++;

        emit Voted(msg.sender, _candidateId);
    }

    function getCandidate(uint256 _candidateId) external view returns (string memory, uint256) {
        return (candidates[_candidateId].name, candidates[_candidateId].votes);
    }
}

2. Cross-Chain Deployment-solidity

We can use LayerZero, Wormhole, or Axelar for cross-chain functionality.

Using LayerZero (Example)

LayerZero allows us to send messages between chains.

Modify the Contract for LayerZero

Add LayerZero’s messaging system:

import "@layerzerolabs/contracts/lzApp/NonblockingLzApp.sol";

contract CrossChainVoting is NonblockingLzApp, Ownable {
    event CrossChainVote(uint16 destinationChainId, uint256 candidateId);

    constructor(address _lzEndpoint) NonblockingLzApp(_lzEndpoint) {}

    function sendVote(uint16 _destinationChainId, uint256 _candidateId) external payable {
        bytes memory payload = abi.encode(_candidateId);
        _lzSend(_destinationChainId, payload, payable(msg.sender), address(0x0), bytes(""), msg.value);
        emit CrossChainVote(_destinationChainId, _candidateId);
    }

    function _nonblockingLzReceive(uint16, bytes memory, uint64, bytes memory _payload) internal override {
        uint256 candidateId = abi.decode(_payload, (uint256));
        candidates[candidateId].votes++;
    }
}

3. Deploying the Smart Contract-solidity

Using Hardhat

npm install --save-dev hardhat @openzeppelin/contracts @layerzerolabs/contracts

Deployment Script (deploy.js)

const hre = require("hardhat");

async function main() {
    const Voting = await hre.ethers.getContractFactory("CrossChainVoting");
    const voting = await Voting.deploy("0xYourLayerZeroEndpointAddress");

    await voting.deployed();
    console.log("Voting contract deployed to:", voting.address);
}

main().catch((error) => {
    console.error(error);
    process.exit(1);
});

Run the Deployment

npx hardhat run scripts/deploy.js --network goerli

4. Frontend Integration (React + Ethers.js)-solidity

Install Dependencies

npm install ethers web3modal

React Frontend Code (App.js)

import { useState, useEffect } from "react";
import { ethers } from "ethers";
import Web3Modal from "web3modal";

const contractAddress = "0xYourContractAddress";
const contractABI = [
  // ABI from compiled contract
];

function App() {
    const [account, setAccount] = useState(null);
    const [candidates, setCandidates] = useState([]);

    useEffect(() => {
        connectWallet();
        fetchCandidates();
    }, []);

    async function connectWallet() {
        const web3Modal = new Web3Modal();
        const connection = await web3Modal.connect();
        const provider = new ethers.providers.Web3Provider(connection);
        const signer = provider.getSigner();
        setAccount(await signer.getAddress());
    }

    async function fetchCandidates() {
        const provider = new ethers.providers.JsonRpcProvider("https://goerli.infura.io/v3/YOUR_INFURA_KEY");
        const contract = new ethers.Contract(contractAddress, contractABI, provider);
        const fetchedCandidates = [];
        
        for (let i = 0; i < 2; i++) {
            const candidate = await contract.getCandidate(i);
            fetchedCandidates.push({ name: candidate[0], votes: candidate[1] });
        }
        setCandidates(fetchedCandidates);
    }

    async function vote(candidateId) {
        const web3Modal = new Web3Modal();
        const connection = await web3Modal.connect();
        const provider = new ethers.providers.Web3Provider(connection);
        const signer = provider.getSigner();
        const contract = new ethers.Contract(contractAddress, contractABI, signer);

        const tx = await contract.vote(candidateId);
        await tx.wait();
        fetchCandidates();
    }

    return (
        <div>
            <h1>Cross-Chain Voting</h1>
            <p>Connected Account: {account}</p>
            {candidates.map((candidate, index) => (
                <div key={index}>
                    <p>{candidate.name}: {candidate.votes} votes</p>
                    <button onClick={() => vote(index)}>Vote</button>
                </div>
            ))}
        </div>
    );
}

export default App;

5. Running the Application

npm start

1. Set Up The Graph for Real-Time Data-solidity

Using The Graph to set up a GraphQL subgraph for real-time updates in your cross-chain voting dApp will allow users to see votes update automatically. Here’s how to integrate it:


Install The Graph CLI

First, install The Graph CLI globally:

npm install -g @graphprotocol/graph-cli

Initialize a New Subgraph

Run the following command and select Ethereum:

graph init --studio cross-chain-voting

Navigate into your project folder:

cd cross-chain-voting

2. Define Smart Contract Events for Indexing

Modify your Solidity contract to include vote-tracking events:

event VoteCasted(address indexed voter, uint256 candidateId, uint256 totalVotes);

Modify the vote function to emit the event:

function vote(uint256 _candidateId) external {
    require(!hasVoted[msg.sender], "You have already voted");
    require(_candidateId < candidateCount, "Invalid candidate");

    hasVoted[msg.sender] = true;
    candidates[_candidateId].votes++;

    emit VoteCasted(msg.sender, _candidateId, candidates[_candidateId].votes);
}

3. Update the Subgraph Manifest (subgraph.yaml)

Modify the subgraph.yaml file to listen to events on your deployed contract:

specVersion: 0.0.4
schema:
  file: ./schema.graphql
dataSources:
  - kind: ethereum/contract
    name: Voting
    network: goerli
    source:
      address: "0xYourContractAddress"
      abi: Voting
      startBlock: 12345678  # Update this with your deployment block
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.5
      language: wasm/assemblyscript
      entities:
        - Vote
      abis:
        - name: Voting
          file: ./abis/Voting.json
      eventHandlers:
        - event: VoteCasted(address,uint256,uint256)
          handler: handleVoteCasted
      file: ./src/mapping.ts

4. Define the GraphQL Schema (schema.graphql)

This schema structures the indexed blockchain data:

type Vote @entity {
  id: ID!
  voter: Bytes!
  candidateId: Int!
  totalVotes: Int!
}

5. Create Event Handlers (src/mapping.ts)

This file will process the event data and store it in The Graph.

import { VoteCasted as VoteCastedEvent } from "../generated/Voting/Voting";
import { Vote } from "../generated/schema";

export function handleVoteCasted(event: VoteCastedEvent): void {
  let vote = new Vote(event.transaction.hash.toHex());
  vote.voter = event.params.voter;
  vote.candidateId = event.params.candidateId.toI32();
  vote.totalVotes = event.params.totalVotes.toI32();
  vote.save();
}

6. Deploy the Subgraph

Generate and Build

graph codegen
graph build

Deploy to Hosted Service

graph deploy --studio cross-chain-voting

This will give you a GraphQL API URL to use in the frontend.


7. Fetch Real-Time Data in React Frontend

Modify the frontend to use Apollo Client for real-time GraphQL updates.

Install Apollo Client

npm install @apollo/client graphql

Update React Code (App.js)

import { useQuery, gql } from "@apollo/client";

const GET_VOTES = gql`
  {
    votes(orderBy: id, orderDirection: desc) {
      id
      voter
      candidateId
      totalVotes
    }
  }
`;

function App() {
    const { loading, error, data } = useQuery(GET_VOTES, { pollInterval: 5000 });

    if (loading) return <p>Loading...</p>;
    if (error) return <p>Error: {error.message}</p>;

    return (
        <div>
            <h1>Cross-Chain Voting</h1>
            {data.votes.map((vote, index) => (
                <p key={index}>
                    Voter: {vote.voter} | Candidate ID: {vote.candidateId} | Total Votes: {vote.totalVotes}
                </p>
            ))}
        </div>
    );
}

export default App;

8. Final Steps

  • Deploy the smart contract on multiple chains.
  • Connect LayerZero (or Axelar) for cross-chain functionality.
  • Deploy GraphQL subgraph to monitor voting events.
  • Use Apollo Client in the frontend for real-time updates.

Comments

No comments yet. Why don’t you start the discussion?

    Leave a Reply

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