
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:
- Smart Contract Development: A Solidity smart contract for voting.
- Cross-Chain Deployment: Using LayerZero, Wormhole, or Axelar for interoperability.
- Frontend Integration: Using React.js and Ethers.js to interact with the contract.
Table of Contents
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.