Une application en ReactJS pour interagir avec le smart contract

Cet article est la suite de l’article sur la mise en place d’un projet blockchain avec Hardhat.

Dans cet article , nous allon sconstgruire une application ReactJs pour interagir avec le smart contract.

Mise en place de l’application ReactJS. N’oubliez pas d’installer ehters.js

npx create-react-app my-token-frontend
cd my-token-frontend
npm install ethers

Création de l’interface de contrat

//   src/ContractABI.js

// This file contains the ABI (Application Binary Interface) for your smart contract
export const CONTRACT_ADDRESS = "0x5FbDB2315678afecb367f032d93F642f64180aa3"; // Replace with your actual contract address

export const CONTRACT_ABI = [
  // Read functions
  "function name() view returns (string)",
  "function symbol() view returns (string)",
  "function decimals() view returns (uint8)",
  "function totalSupply() view returns (uint256)",
  "function balanceOf(address) view returns (uint256)",
  "function allowance(address owner, address spender) view returns (uint256)",
  
  // Write functions
  "function transfer(address to, uint amount) returns (bool)",
  "function approve(address spender, uint256 amount) returns (bool)",
  "function transferFrom(address from, address to, uint256 amount) returns (bool)",
  "function mint(address to, uint256 amount)",
  
  // Events
  "event Transfer(address indexed from, address indexed to, uint amount)",
  "event Approval(address indexed owner, address indexed spender, uint256 value)"
];

Création du composant App

//src/App.js

import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers';
import { CONTRACT_ADDRESS, CONTRACT_ABI } from './ContractABI';
import './App.css';

function App() {
  const [account, setAccount] = useState('');
  const [signer, setSigner] = useState(null);
  const [contract, setContract] = useState(null);
  const [tokenName, setTokenName] = useState('');
  const [tokenSymbol, setTokenSymbol] = useState('');
  const [balance, setBalance] = useState('0');
  const [transferTo, setTransferTo] = useState('');
  const [transferAmount, setTransferAmount] = useState('');
  const [mintTo, setMintTo] = useState('');
  const [mintAmount, setMintAmount] = useState('');
  const [isConnected, setIsConnected] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  const [errorMessage, setErrorMessage] = useState('');
  const [successMessage, setSuccessMessage] = useState('');

  useEffect(() => {
    // Check if already connected
    checkConnection();

    // Listen for account changes
    if (window.ethereum) {
      window.ethereum.on('accountsChanged', (accounts) => {
        if (accounts.length > 0) {
          connectWallet();
        } else {
          setIsConnected(false);
          setAccount('');
        }
      });
    }
  }, []);

  const checkConnection = async () => {
    if (window.ethereum) {
      try {
        const provider = new ethers.BrowserProvider(window.ethereum);
        const accounts = await provider.listAccounts();
        
        if (accounts.length > 0) {
          connectWallet();
        }
      } catch (error) {
        console.error("Error checking connection:", error);
      }
    }
  };

  const connectWallet = async () => {
    setErrorMessage('');
    setSuccessMessage('');
    setIsLoading(true);

    try {
      if (!window.ethereum) {
        setErrorMessage("MetaMask is not installed. Please install MetaMask to use this application.");
        setIsLoading(false);
        return;
      }

      // Request account access
      const provider = new ethers.BrowserProvider(window.ethereum);
      await provider.send("eth_requestAccounts", []);
      
      const signer = await provider.getSigner();
      const address = await signer.getAddress();
      
      const tokenContract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, signer);
      
      setAccount(address);
      setSigner(signer);
      setContract(tokenContract);
      setIsConnected(true);

      // Get token info
      const name = await tokenContract.name();
      const symbol = await tokenContract.symbol();
      setTokenName(name);
      setTokenSymbol(symbol);
      
      // Get balance
      updateBalance(tokenContract, address);
      
      setSuccessMessage("Wallet connected successfully!");
    } catch (error) {
      console.error("Connection error:", error);
      setErrorMessage("Failed to connect wallet: " + error.message);
    } finally {
      setIsLoading(false);
    }
  };

  const updateBalance = async (tokenContract, address) => {
    try {
      const balance = await tokenContract.balanceOf(address);
      setBalance(ethers.formatEther(balance));
    } catch (error) {
      console.error("Error getting balance:", error);
    }
  };

  const handleTransfer = async (e) => {
    e.preventDefault();
    setErrorMessage('');
    setSuccessMessage('');
    setIsLoading(true);

    try {
      if (!ethers.isAddress(transferTo)) {
        throw new Error("Invalid recipient address");
      }

      const amount = ethers.parseEther(transferAmount);
      const tx = await contract.transfer(transferTo, amount);
      
      await tx.wait();
      
      setSuccessMessage(`Successfully transferred ${transferAmount} ${tokenSymbol} to ${transferTo}`);
      setTransferAmount('');
      
      // Update balance
      updateBalance(contract, account);
    } catch (error) {
      console.error("Transfer error:", error);
      setErrorMessage("Transfer failed: " + error.message);
    } finally {
      setIsLoading(false);
    }
  };

  const handleMint = async (e) => {
    e.preventDefault();
    setErrorMessage('');
    setSuccessMessage('');
    setIsLoading(true);

    try {
      if (!ethers.isAddress(mintTo)) {
        throw new Error("Invalid recipient address");
      }

      const amount = ethers.parseEther(mintAmount);
      const tx = await contract.mint(mintTo, amount);
      
      await tx.wait();
      
      setSuccessMessage(`Successfully minted ${mintAmount} ${tokenSymbol} to ${mintTo}`);
      setMintAmount('');
      
      // Update balance if minted to self
      if (mintTo.toLowerCase() === account.toLowerCase()) {
        updateBalance(contract, account);
      }
    } catch (error) {
      console.error("Mint error:", error);
      setErrorMessage("Mint failed: " + error.message);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div className="App">
      <header className="App-header">
        <h1>{tokenName || 'MyToken'} Interface</h1>
        
        {!isConnected ? (
          <button onClick={connectWallet} disabled={isLoading}>
            {isLoading ? 'Connecting...' : 'Connect Wallet'}
          </button>
        ) : (
          <div className="connected-container">
            <p>Connected Account: {account}</p>
            <p>Balance: {balance} {tokenSymbol}</p>
            
            <div className="card">
              <h2>Transfer Tokens</h2>
              <form onSubmit={handleTransfer}>
                <div className="form-group">
                  <label>Recipient Address:</label>
                  <input 
                    type="text" 
                    value={transferTo} 
                    onChange={(e) => setTransferTo(e.target.value)}
                    placeholder="0x..."
                    required
                  />
                </div>
                <div className="form-group">
                  <label>Amount:</label>
                  <input 
                    type="text" 
                    value={transferAmount} 
                    onChange={(e) => setTransferAmount(e.target.value)}
                    placeholder="0.0"
                    required
                  />
                </div>
                <button type="submit" disabled={isLoading}>
                  {isLoading ? 'Processing...' : 'Transfer'}
                </button>
              </form>
            </div>
            
            <div className="card">
              <h2>Mint Tokens (Owner Only)</h2>
              <form onSubmit={handleMint}>
                <div className="form-group">
                  <label>Recipient Address:</label>
                  <input 
                    type="text" 
                    value={mintTo} 
                    onChange={(e) => setMintTo(e.target.value)}
                    placeholder="0x..."
                    required
                  />
                </div>
                <div className="form-group">
                  <label>Amount:</label>
                  <input 
                    type="text" 
                    value={mintAmount} 
                    onChange={(e) => setMintAmount(e.target.value)}
                    placeholder="0.0"
                    required
                  />
                </div>
                <button type="submit" disabled={isLoading}>
                  {isLoading ? 'Processing...' : 'Mint'}
                </button>
              </form>
            </div>
          </div>
        )}
        
        {errorMessage && <p className="error-message">{errorMessage}</p>}
        {successMessage && <p className="success-message">{successMessage}</p>}
      </header>
    </div>
  );
}

export default App;

Un peu de style

//  src/App.css

.App {
  text-align: center;
  font-family: Arial, sans-serif;
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  font-size: calc(10px + 1vmin);
  color: white;
  padding: 20px;
}

button {
  background-color: #61dafb;
  border: none;
  color: #282c34;
  padding: 10px 20px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  margin: 10px 2px;
  cursor: pointer;
  border-radius: 4px;
  font-weight: bold;
}

button:disabled {
  background-color: #cccccc;
  cursor: not-allowed;
}

.connected-container {
  width: 100%;
  max-width: 800px;
}

.card {
  background-color: #3a3f4b;
  border-radius: 8px;
  padding: 20px;
  margin: 20px 0;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}

.form-group {
  margin-bottom: 15px;
  text-align: left;
}

label {
  display: block;
  margin-bottom: 5px;
}

input {
  width: 100%;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 16px;
}

.error-message {
  color: #ff6b6b;
  background-color: rgba(255, 107, 107, 0.1);
  padding: 10px;
  border-radius: 4px;
  margin: 10px 0;
}

.success-message {
  color: #51cf66;
  background-color: rgba(81, 207, 102, 0.1);
  padding: 10px;
  border-radius: 4px;
  margin: 10px 0;
}

Lancement du front

npm start

Configuration de MetaMask pour utiliser la blockchain locale

L’ajout de Hardhat se fait comme pour tout ajout de blockchain.

Vérifiez que vous êtes bien connecté sur la blockchain locale, ensuite ajoutez un compte à votre wallet, à partir de la clé privée, il y a 20 comptes qui sont listés lorsque vous déployez le smart contract

On arrive sur cet écran :

On peut faire un transfert de token vers une autre addresse

Account #10: 0xBcd4042DE499D14e55001CcbB24a551F3b954096 (10000TH)
Private Key: 0xf214f2b2cd398c806f84e317254e0f0b801d0643303237da22a48e01628897

On va vérifier que le compte 10 a bien reçu les tokens. Pour ce faire on va aller dans la shell de Hardhat

npx hardhat console --network localhost
Retour en haut