Tutoriel Complet : Créer un Frontend pour Interagir avec des Smart Contracts sur Morph
Apercu
Dans "Comment Construire une DApp sur Morph : Un Guide Complet", vous avez appris à configurer et déployer vos smart contracts iToken et iTokenFaucet en utilisant Hardhat. Maintenant, il est temps de donner vie à ces contrats avec une application frontend conviviale.
Dans ce tutoriel, nous vous guiderons à travers la configuration d’une application React intégrée à Wagmi v2 et RainbowKit pour interagir avec vos contrats déployés sur le réseau Morph Holesky. Vous apprendrez à connecter des portefeuilles, afficher les soldes de jetons et permettre aux utilisateurs de réclamer des jetons depuis le faucet.
Dans ce tutoriel, nous allons configurer une application frontend React en utilisant Vite avec TypeScript, intégrer Wagmi v2 pour les interactions avec Ethereum, RainbowKit pour la connexion des portefeuilles, et Tailwind CSS pour le style. Nous nous assurerons que toutes les configurations sont conformes aux dernières directives de migration de Wagmi v2.
Prérequis
Node.js (v14 ou supérieur)
npm (v6 ou supérieur)
Git
Contrats iToken et iTokenFaucet déployés sur le réseau Morph Holesky
Guide Étape par Étape
Initialiser un Nouveau Projet React TypeScript avec Vite
Commencez par créer un nouveau projet React en utilisant Vite avec le modèle TypeScript :npm create vite@latest morph-frontend -- --template react-ts cd morph-frontend
Initialiser un Dépôt Git
git init
Installer les Dépendances
Installez les dépendances nécessaires :npm install @rainbow-me/rainbowkit wagmi viem@2.x @tanstack/react-query ethers npm install -D tailwindcss postcss autoprefixer
Configurer Tailwind CSS
InitialisezTailwind CSS
:npx tailwindcss init -p
Mettez à jour
tailwind.config.cjs
:/** @type {import('tailwindcss').Config} */ module.exports = { content: [ "./index.html", "./src/**/*.{js,ts,jsx,tsx}", ], theme: { extend: {}, }, plugins: [], }
Mettez à jour
postcss.config.cjs
:module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, }
Ajoutez les directives Tailwind à
src/index.css
:@tailwind base; @tailwind components; @tailwind utilities;
Définir les Constantes du Contrat
Créezsrc/constants.ts
:export const ITOKEN_ADDRESS = '0xYourITokenAddressHere' export const ITOKEN_FAUCET_ADDRESS = '0xYourITokenFaucetAddressHere' export const ITOKEN_ABI = [ 'function name() view returns (string)', 'function symbol() view returns (string)', 'function decimals() view returns (uint8)', 'function balanceOf(address owner) view returns (uint256)', 'function transfer(address to, uint amount) returns (bool)', 'event Transfer(address indexed from, address indexed to, uint amount)', ] export const ITOKEN_FAUCET_ABI = [ 'function claim() external', 'function drain() external', 'function dailyClaim() view returns (uint256)', 'function lastClaimed(address) view returns (uint256)', 'event Claimed(address indexed *claimer, uint256 *amount)', ]
Configurer les Hooks de Contrat
Créezsrc/hooks/useContracts.ts
:import { ITOKEN_ADDRESS, ITOKEN_ABI, ITOKEN_FAUCET_ADDRESS, ITOKEN_FAUCET_ABI } from '../constants' export const useIToken = () => ({ address: ITOKEN_ADDRESS, abi: ITOKEN_ABI, }) export const useITokenFaucet = () => ({ address: ITOKEN_FAUCET_ADDRESS, abi: ITOKEN_FAUCET_ABI, })
Configurer le Point d'Entrée Principal
Mettez à joursrc/main.tsx
:```typescript import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' import './index.css' import '@rainbow-me/rainbowkit/styles.css' import { RainbowKitProvider, getDefaultWallets, darkTheme, } from '@rainbow-me/rainbowkit' import { createConfig, WagmiProvider } from 'wagmi' import { http } from 'wagmi' import { mainnet, sepolia } from 'wagmi/chains' import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const morphTestnet = { id: 2810, name: 'Morph Holesky', network: 'morphTestnet', nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18, }, rpcUrls: { default: ['rpc-quicknode-holesky.morphl2.io'], }, blockExplorers: { default: { name: 'Blockscout', url: 'explorer-holesky.morphl2.io' }, }, testnet: true, }
const queryClient = new QueryClient()
const { connectors } = getDefaultWallets({ appName: 'Morph Frontend', projectId: 'YOUR_PROJECT_ID', chains: [morphTestnet, mainnet, sepolia], })
const config = createConfig({ chains: [morphTestnet, mainnet, sepolia], transports: {
}, connectors, })
ReactDOM.createRoot(document.getElementById('root')!).render( , )
8. **Mettre à Jour le Composant App**
Mettez à jour `src/App.tsx` :
```typescript
import React, { useEffect } from 'react'
import { ConnectButton } from '@rainbow-me/rainbowkit'
import { useSwitchChain } from 'wagmi'
import { useAccount } from 'wagmi'
import Balances from './components/Balances'
import Faucet from './components/Faucet'
function App() {
const { chain, isConnected } = useAccount()
const { switchChain } = useSwitchChain()
useEffect(() => {
if (isConnected && chain?.id !== 2810) {
switchChain?.(2810)
}
}, [isConnected, chain, switchChain])
return (
<div className="min-h-screen flex flex-col items-center justify-center bg-gray-100 p-4">
<h1 className="text-4xl font-bold mb-8">Morph L2 DApp</h1>
<ConnectButton />
{isConnected && chain?.id !== 2810 && (
<p className="mt-4 text-red-500">
Please switch to the Morph Holesky network.
</p>
)}
{isConnected && chain?.id === 2810 && (
<>
<Balances />
<Faucet />
</>
)}
</div>
)
}
export default App
Créer le Composant Balances
Créezsrc/components/Balances.tsx
:import React, { useEffect, useState } from 'react' import { useAccount, useReadContracts } from 'wagmi' import { useIToken } from '../hooks/useContracts' import { erc20Abi } from 'viem' import { formatUnits } from 'viem' const Balances: React.FC = () => { const { address, isConnected } = useAccount() const iToken = useIToken() const [balance, setBalance] = useState<string>('0') const { data, isError, isLoading } = useReadContracts({ contracts: [ { address: iToken.address, abi: erc20Abi, functionName: 'balanceOf', args: [address!], }, { address: iToken.address, abi: erc20Abi, functionName: 'decimals', }, { address: iToken.address, abi: erc20Abi, functionName: 'symbol', }, ], }) useEffect(() => { if (data && data[0] && data[1]) { const rawBalance = data[0].result as bigint const decimals = Number(data[1].result) const formatted = formatUnits(rawBalance, decimals) setBalance(formatted) } }, [data]) if (!isConnected) { return null } if (isLoading) { return <p className="mt-8 text-xl">Loading balance...</p> } if (isError) { return <p className="mt-8 text-red-600">Error fetching balance.</p> } return ( <div className="mt-8 bg-white shadow-lg rounded-lg p-6"> <h2 className="text-2xl font-semibold">Your iToken Balance</h2> <p className="mt-2 text-xl">{balance} IT</p> </div> ) } export default Balances
Créer le Composant Faucet
Créezsrc/components/Faucet.tsx
:import React, { useState } from 'react' import { useITokenFaucet } from '../hooks/useContracts' import { useAccount, useWriteContract, useSimulateContract } from 'wagmi' const Faucet: React.FC = () => { const { isConnected } = useAccount() const faucet = useITokenFaucet() const [loading, setLoading] = useState<boolean>(false) const [message, setMessage] = useState<string>('') const { data: simulationData, isLoading: isSimulating } = useSimulateContract({ address: faucet.address, abi: faucet.abi, functionName: 'claim', args: [], }) const { writeContract: claimWrite, isPending: isWriting } = useWriteContract() const handleClaim = () => { setLoading(true) setMessage('') claimWrite?.({ address: faucet.address, abi: faucet.abi, functionName: 'claim', args: [], }, { onSuccess() { setMessage('Claim successful!') setLoading(false) }, onError(error: any) { setMessage(error.message || 'Claim failed') setLoading(false) }, }) } return ( <div className="mt-8 bg-white shadow-lg rounded-lg p-6"> <h2 className="text-2xl font-semibold">iToken Faucet</h2> <button onClick={handleClaim} disabled={!isConnected || loading || isSimulating} className={`px-4 py-2 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75 disabled:opacity-50`} > {loading || isSimulating ? 'Claiming...' : 'Claim 1000 IT'} </button> {message && <p className="mt-2 text-green-600">{message}</p>} {(isWriting || isSimulating) && <p className="mt-2">Processing...</p>} </div> ) } export default Faucet
Configurer .gitignore
Mettez à jour.gitignore
:# dependencies /node_modules # production /dist # misc .DS_Store .env .env.local .env.development.local .env.test.local .env.production.local # local env files *.local # logs npm-debug.log* yarn-debug.log* yarn-error.log* # editor directories and files .vscode/ .idea/ *.suo *.ntvs* *.njsproj *.sln
Dernières Étapes
Remplacez les Variables :
Dans
src/constants.ts
, remplacez '0xYourITokenAddressHere
' et '0xYourITokenFaucetAddressHere
' par les adresses réelles de vos contrats.Dans
src/main.tsx
, remplacez 'YOUR_PROJECT_ID
' par votre identifiant de projet RainbowKit.
Lancer le Serveur de Développement
npm run dev
Ouvrez votre navigateur et accédez à http://localhost:5173 pour voir votre application.
Conclusion
Félicitations ! Vous avez configuré avec succès une application frontend React intégrée à Wagmi v2, RainbowKit et Tailwind CSS. Votre application permet désormais aux utilisateurs de :
Connecter leurs portefeuilles Ethereum via RainbowKit
Basculer automatiquement sur le réseau Morph Holesky
Voir leur solde de iToken
Réclamer des iTokens depuis le faucet
Points Clés :
Intégration de Wagmi v2 : Configurée correctement avec QueryClientProvider, WagmiProvider et RainbowKitProvider.
Interactions avec les Smart Contracts: Utilisation des hooks useReadContracts, useWriteContract et useSimulateContract pour des interactions efficaces avec les contrats.
Gestion des Erreurs : Mise en place d'une gestion des erreurs robuste et des mécanismes de retour d'information pour l'utilisateur.
Tailwind CSS : Configuré pour un style efficace et évolutif.
Prochaines Étapes :
Améliorer l'UI/UX : Personnalisez davantage vos composants avec Tailwind CSS.
Étendre les Fonctionnalités : Ajoutez des fonctionnalités comme le staking ou la gouvernance.
Déploiement : Préparez votre application pour un déploiement sur des plateformes comme Vercel, Netlify ou GitHub Pages.
Bon code! 🚀