Decentralized applications (dApps) are at the forefront of the blockchain revolution because they are the window into the web3 world. The importance of DApps cannot be overstated, as they represent the bridge between the traditional web and the decentralized web, where users have control over their data and interactions.
One of the critical components of DApps is the wallet connector, which facilitates the interaction between the DApp and the user's wallet. This connection is essential for authenticating users and ensuring transactions are securely executed on the blockchain.
However, the current landscape of DApp/Wallet connectors faces challenges, as connecting to web wallets has been a hassle. This complexity arises from the variety of wallet types and the intricacies of establishing a secure and seamless connection between DApps and user wallets. To address this issue, intmax-walletsdk
was created. intmax-walletsdk
is built to enable direct communication and connection between web-based wallets and dApps, leveraging the power of EIP1193-like interactions to enhance user experiences across the blockchain ecosystem. This SDK allows you to easily connect your web3 dApp to any web wallet. By simplifying the integration process, intmax-walletsdk
not only makes it easier for both wallet and dapps developers to build but also enhances the user experience by providing a more straightforward and secure method for wallet authentication and interaction.
In this article, we will delve into the process of building a simple dApp for conducting decentralized sports voting, using Vite, React, TypeScript, and intmax-walletsdk
for wallet authentication and connection. This DApp will demonstrate the simplicity of integrating the INTMAX Wallet into a DApp. By following the steps outlined in this guide, we aim to show that building a DApp and connecting it to the INTMAX Wallet doesn't have to be hard and is, in fact, very simple.
Prerequisites
Node.js 18.0+
Familiarity with the command line interface.
Building the dApp
Step 1: Setting Up the Environment
For this tutorial, we will start by creating a new React project with TypeScript and Vite, a modern front-end build tool, to set up a development server. This is done by running the following commands in the terminal:
npm create vite@latest my-voting-dapp -- --template react-ts
cd my-voting-dapp
npm install
npm run dev
This command creates a new React project named my-voting-dapp
Step 2: Install INTMAX Wallet SDK and Ethers.js
To enhance our dApp's functionality, we will integrate a wallet SDK, specifically intmax-walletsdk, which enables users to connect their INTMAX wallets and cast their votes. This step is crucial for authenticating users and ensuring that each wallet can only vote once, thereby maintaining the integrity of the voting process. Install the necessary packages by:
npm install ethers @intmax-walletsdk
Step 3: Creating the Voting Component
The core of our dApp is the voting component, where users can select their favorite club and submit their votes. This component is designed with simplicity in mind, with a straightforward interface that allows users to select their favorite club and submit their votes.
Create a component folder in the src
directory of your project. and then create a new file named Voting.tsx
inside. Copy and paste the provided code for the Voting
component into this file.
// src/components/Voting.tsx
import { useState } from 'react';
const Voting = () => {
return ();
};
export default Voting;
Import the Voting component in App.tsx with the code below:
// App.tsx
import Voting from './components/Voting'
function App() {
return (
<>
<div>
<Voting/>
</div>
</>
)
}
export default App
Step 4: Importing Required Modules
Connecting our dApp to INTMAX Wallet is a seamless process, thanks to the intmax-walletsdk
library.
You need to import the intmaxDappClient
from the intmax-walletsdk/dapp
package.
This client will be responsible for handling the connection and authentication process with the INTMAX Wallet.
In your main component file (Voting.jsx
), import the necessary modules:
// src/components/Voting.tsx
import { useState, useEffect } from 'react';
import { ethereumProvider, intmaxDappClient } from "intmax-walletsdk/dapp";
import { ethers } from 'ethers';
Step 5: Defining the Smart Contract Interface
This is a decentralized voting dapp, hence, it requires a smart contract. Define the ABI and contract address for your smart contract. Replace 'your-contract-address'
with your actual contract address.
const abi = [/* Voting contract ABI in Github Gist below */];
const contractAddress = '0x4bc4154b03B7fBbE72CBFA33aDe77BB820FbB337'; // Deployed contract address
ABI in GitHub Gist here.
Step 6: Creating the SDK Instance
Create an instance of the Intmax SDK by configuring the intmaxDappClient
, and setting up the necessary parameters which includes the default wallet URL of choice. In this tutorial, we will be using the INTMAX Wallet.
The necessary parameters include the name of your DApp, the URL of the wallet you wish to connect to, the metadata of your DApp, and a provider.
const DEFAULT_WALLET_URL = "https://wallet.intmax.io";
const DEFAULT_DAPP_ICON = `${window.location.origin}/vite.svg` as string;
const DAPP_METADATA = {
name: "INTMAX Dapp Example",
description: "This is a simple example of how to use the sdk dapp client.",
icons: [DEFAULT_DAPP_ICON],
};
const createsdk = (walletUrl: string) => {
return intmaxDappClient({
wallet: { url: walletUrl, name: "INTMAX Wallet", window: { mode: "popup" } },
metadata: DAPP_METADATA,
providers: { eip155: ethereumProvider() },
});
};
const sdk = createsdk(DEFAULT_WALLET_URL);
The createsdk
function initializes the INTMAX Wallet SDK with the default wallet URL and metadata.
Step 7: Initializing State Variables
For proper react state management, initialize state variables for storing selected club, vote counts, accounts, results, voted addresses, and the contract instance:
const [selectedClub, setSelectedClub] = useState('');
const [voteCounts, setVoteCounts] = useState({ ClubA: 0, ClubB: 0, ClubC: 0 });
const [accounts, setAccounts] = useState([]);
const [result, setResult] = useState("");
const [votedAddresses, setVotedAddresses] = useState([]);
const [contract, setContract] = useState(null);
Step 8: Handling Wallet Connection
Next, we create a handleConnect
function. Inside the handleConnect
function, we use the SDK to request accounts from INTMAX Wallet. We then save the account in the React state to be used in the dapp. When the "Connect Wallet" button is clicked, it triggers the handleConnect
function.
Implement a handleConnect
function to connect to the wallet by:
const handleConnect = async () => {
const ethereum = sdk.provider("eip155:137");
await ethereum.request({ method: "eth_requestAccounts", params: [] });
const accounts = (await ethereum.request({ method: "eth_accounts", params: [] })) as string[];
setAccounts(accounts);
};
Step 9: Signing Messages and Interacting with the Contract
Next, we create a handleSignMessage
function that sends a message to the smart contract with the user's account to cast a vote for the selected club. The selected club is passed to this function when a user clicks on it. After sending the message, the vote count for the selected club is incremented.
const handleSignMessage = async (club: string) => {
// Connects with wallet account
if (accounts.length === 0) await handleConnect();
const ethereum = sdk.provider("eip155:137"); // 137 is polygon network
const _accounts = (await ethereum.request({ method: "eth_accounts", params: [] })) as string[];
const currentAccount = _accounts[0];
// Checks if account has voted already
if (votedAddresses.includes(currentAccount)) {
alert("You have already voted.");
return;
}
// Checks for the index of selected club
const clubIndex = ['Club A', 'Club B', 'Club C'].indexOf(club);
console.log('clubIndex:', clubIndex);
// Convert the club index to a uint256 value for smart contract
const clubId = BigInt(clubIndex).toString(16);
console.log('clubId:', clubId);
try {
// To interact with smart contract, Using ethers.js to create an RPC provider to connect to a node, to interact with smart contract
const provider = new ethers.JsonRpcProvider('https://polygon-mainnet.infura.io/v3/YOUR-RPC-KEY');
// Creates a signer from the provider
const signer = new JsonRpcSigner(provider, currentAccount);
// Define the contract interface
const contract = new ethers.Contract(contractAddress, abi, signer);
// Sends a transaction to cast a vote to smart contract
const result = await ethereum.request({
method: "eth_sendTransaction",
params: [{
from: currentAccount,
to: contractAddress,
data: contract.interface.encodeFunctionData("vote", [clubId]),
gasLimit: 300000,
}]
});
// Update vote counts and voted addresses
setResult(result as string);
setVoteCounts(prev => ({ ...prev, [club]: (prev[club] || 0) + 1 }));
setVotedAddresses(prev => [...prev, currentAccount]);
} catch (error) {
console.error("Error voting:", error);
}
};
Step 10: Fetching Initial Vote Counts
To read the smart contract and update the dapp with the current vote counts, use useEffect
to fetch initial vote counts from the contract.
useEffect(() => {
if (!contract) {
// Initialize the contract instance once
const provider = new ethers.JsonRpcProvider('https://polygon-mainnet.infura.io/v3/YOUR-RPC-KEY');
console.log('provider:', provider);
const signer = new JsonRpcSigner(provider, "YOUR-INTMAX-WALLET-ADDRESS");
console.log('signer:', signer);
const contractInstance = new ethers.Contract(contractAddress, abi, signer);
setContract(contractInstance);
// Fetch initial vote counts from the contract
const fetchVoteCounts = async () => {
const counts: Record<string, number> = {};
const clubs = ['Club A', 'Club B', 'Club C'];
for (const club of clubs) {
const clubId = BigInt(clubs.indexOf(club)).toString(16);
const votes = await contractInstance.getClubVotes(clubId);
counts[club] = parseInt(votes.toString(), 10);
}
setVoteCounts(counts);
};
fetchVoteCounts();
}
}, [contract]);
This also updates the state after each vote is made.
Step 11: Rendering the Component
Render the component UI, including buttons for connecting the wallet, casting votes, and displaying vote counts:
return (
<div className="flex flex-col items-center min-h-screen bg-gray-100">
<div className="w-full bg-blue-500 p-4 flex justify-between items-center sticky top-0 z-10">
<h1 className="text-2xl font-bold text-white">Sports Voting </h1>
<button
onClick={handleConnect}
className="bg-white hover:bg-gray-200 text-black font-bold py-2 px-4 rounded"
>
{accounts.length > 0 ? `Connected: ${truncateAddress(accounts[0])}` : 'Connect Wallet'}
</button>
</div>
<div className='flex flex-col justify-center items-center mb-[60px] mt-[100px]'>
<h1 className="text-4xl font-bold mb-8 px-1">Smart Contract Voting</h1>
<div className="flex flex-col space-y-1 sm:flex-row sm:space-x-4">
{['Club A', 'Club B', 'Club C'].map(club => (
<div
key={club}
className={`relative p-4 w-[200px] h-[200px] hover:border-blue-500 group`}
onClick={() => {
setSelectedClub(club);
handleSignMessage(club);
}}
>
<img
src={`/${club.replace(' ', '')}.png`}
alt={club}
className="absolute inset-0 w-full h-full object-cover bg-black"
/>
<div
className={`absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity`}
>
<p className="text-white text-center ">Vote for {club}</p>
</div>
</div>
))}
</div>
<div className="flex flex-col space-y-0 sm:flex-row sm:space-x-4 mt-8">
{['Club A', 'Club B', 'Club C'].map(club => (
<div key={club}>
<p className="text-center">{club} Votes: {voteCounts[club]}</p>
</div>
))}
</div>
<div className='flex flex-col justify-center items-center mt-[70px]'>
<h1 className="text-4xl font-bold mb-4">How to Vote</h1>
<div className="bg-white p-6 rounded shadow">
<h2 className="text-2xl font-bold mb-4">Steps to Connect and Vote:</h2>
<ol className="list-decimal list-inside">
<li className="mb-2">Click on the "Connect Wallet" button.</li>
<li className="mb-2">Login to your INTMAX Wallet.</li>
<li className="mb-2">Connect your wallet to the DApp.</li>
<li className="mb-2">Choose your favorite club from the options.</li>
<li className="mb-2">Click on the club to cast your vote.</li>
</ol>
</div>
</div>
</div>
</div>
);
Step 12: Running the Dapp
With everything set up, run your DApp locally using the following command:
npm run dev
Note:
Get the exact images used in the demo here
To install Tailwind as used in this project, follow this guide on their official website here. For the exact code, check the Github repo here. Feel free to add your styles to customize the appearance of the DApp.
Visit http://localhost:5173 in your browser to view the voting DApp. Users can now connect to their Intmax Wallet, choose their favorite club, and cast their vote.
That's it! Your dApp should look like this.
You've successfully built a simple voting DApp using React, Vite, Typescript, and the INTMAX Wallet SDK, allowing users to vote for their favorite sports club by connecting to their INTMAX Wallet. To get the full code, please check the GitHub repo here.
Conclusion
This tutorial provides a basic overview of creating a decentralized voting platform (dApp) using the Intmax Wallet SDK and Ethers.js.
Building a decentralized voting platform (dApp) and integrating the INTMAX Wallet is a straightforward process that doesn't require extensive knowledge of blockchain technology. The steps outlined in this article provide a clear pathway for developers to create a functional app that allows users to vote securely and transparently.
Developers can expand upon this foundation by adding features such as handling errors, improving UI/UX, and integrating additional functionalities as needed.
The simplicity of this process underscores the potential of blockchain technology to revolutionize the way we interact with digital platforms. By making it easy to build and connect dApps to the blockchain, we open up new possibilities for innovation and user engagement.
Quick Links
Learn more about Intmax-walletsdk: https://intmax-wallet.gitbook.io/intmax-walletsdk
Live demo: sports-voting-demo.vercel.app
Full code in Github Rep: emmaglorypraise/Sports-Voting-Demo (github.com)
Smart contract code and ABI: GitHub Gist
Reach out to me on Twitter if you have any questions.