Interagir avec des Smart Contract sur Morph avec Viem

Daniel KambaleDaniel Kambale
6 min read

Dans ce tutoriel, je vais vous guider sur la façon d'interagir avec des Smart Contracts déployés sur Morph Holesky via l'interface frontend avec Viem.

Qu'est-ce que Viem?

Viem est une interface TypeScript d’ Ethereum qui fournit des outils de base pour interagir avec Ethereum.

Points de départ

Vous aurez besoin d'ajouter Morph Holesky à votre portefeuille Metamask ou EVM si vous ne l'avez pas encore , vous pouvez le faire ici : Détails du réseau Morph Holesky.

  • Obtenez un peu de $ETH pour les tests

  • Transférez votre ETH vers Morph Holesky ETH ici

  • Avoir Nodejs installé sur votre ordinateur

  • Avoir une compréhension de base de JavaScript et d'un de ses frameworks ou bibliothèques, et être prêt à développer.

Déploiement de Smart Contract sur Morph Holesky

J'ai un Smart Contract déployé avec son code source disponible sur GitHub. Ce contrat inclut une fonction pour stocker et récupérer des nombres sur la blockchain.

Ce contrat est déployé sur Morph Holesky. Vous pouvez l'explorer ici :

Commençons

Nous avons déployé notre Smart Contract et nous sommes maintenant prêts à interagir avec lui via l'interface frontend en utilisant Viem. Nous allons utiliser React.js pour cela et l'installer avec Vite.js.

# npm 7+, extra double-dash is needed:
npm create vite@latest simple-contract-ui -- --template react

Après l'installation, ouvrez le dossier du projet dans votre IDE préféré, comme VS Code.

cd simple-contract-ui && code .

Maintenant que vous avez ouvert le dossier du projet dans VS Code, vous pouvez installer Viem.

npm i viem

Remplacer le composant App

Nous devons remplacer le contenu dans app.jsx, de ceci :

import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

function App() {
  const [count, setCount] = useState(0)

  return (
    <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.jsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}

export default App

à cela :

import { useState } from 'react'
import Morph from './assets/Morph.svg'
import viteLogo from '/vite.svg'
import './App.css'
import { createPublicClient, http } from 'viem'
import { morphHolesky } from 'viem/chains'

const client = createPublicClient({
  chain: morphHolesky,
  transport: http(),
})

function App() {
  const [number, setNumber] = useState(0)
  return (
    <>
      <div className="logos">
        <a href="https://vite.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://morphl2.io" target="_blank">
          <img src={Morph} className="mlogo" alt="Vite logo" />
        </a>
      </div>
      <h1>Vite + Morph</h1>
      <div className="card">
        <button>
          Number in storage is {number}
        </button>

      </div>
    </>
  )
}

export default App

J'ai initialisé Viem dans le projet et fait quelques ajustements dans l'interface utilisateur. Maintenant, passons à l'utilisation de Viem pour rendre le projet réactif. D'abord, importons createWalletClient depuis Viem.

import { createPublicClient, http, createWalletClient, custom,parseEther  } from 'viem'

Ensuite, initialisons-le dans le code.

const wallet_client = createWalletClient({
  chain: morphHolesky,,
  transport: custom(window.ethereum)
})

Pour accéder au portefeuille disponible dans le navigateur, nous utilisons window.ethereum. Vous devrez copier l'ABI du contrat vers votre interface frontend. Créez un nouveau dossier nommé contract à l'intérieur du dossier src, puis créez un fichier appelé abi.js.

const abi = [
    {
        "inputs": [],
        "name": "retrieve",
        "outputs": [
            {
                "internalType": "uint256",
                "name": "",
                "type": "uint256"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [
            {
                "internalType": "uint256",
                "name": "_number",
                "type": "uint256"
            }
        ],
        "name": "store",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    }
]

Ensuite, nous l'importerons dans App.jsx.

import ABI from "./contract/abi"

Nous avons fini d'importer l'ABI. Maintenant, créons une fonction appelée store à l'intérieur de la fonction App.

 const store = async (number) => {
    const { request } = await client.simulateContract({
      address: '0x599d52A441Eef87f99241396ee14E4C9fFA78281',
      abi: ABI,
      functionName: 'store',
      args: [parseEther(number)]
    });

    const hash = await wallet_client.writeContract(request);

    return hash;
  }

La fonction store est utilisée pour interagir avec la fonction store du contrat. Dans le code ci-dessus, nous passons le paramètre _number du contrat en utilisant l'option args. Le paramètre est formaté avec ethers pour que le contrat puisse le comprendre.

Récupération des données depuis le Smart Contract

Ne vous inquiétez pas, vous verrez ça ! Pour afficher la sortie du Smart Contract, nous devons faire un appel au contrat. De la même manière que des appels API classiques avec Axios ou Fetch, nous utiliserons Viem pour récupérer la demande depuis le Smart Contract via l'ABI. Ajoutez le code suivant après la fonction store.

const retrieve = async () => {
    const data = await client.readContract({
      address: '0x599d52A441Eef87f99241396ee14E4C9fFA78281',
      abi: ABI,
      functionName: 'retrieve',
      account
    });

    return data;
  }

Pour utiliser la dApp, nous devons connecter un portefeuille. Nous allons créer un hook React appelé useConnect pour faciliter la connexion des portefeuilles. Commencez par créer un dossier hooks et un fichier useConnect.js à l'intérieur. Collez le code suivant dans le fichier :

import { useState, useEffect } from 'react';

export const useWallet = () => {
  const [account, setAccount] = useState(null);
  const [error, setError] = useState(null);

  // Function to connect wallet
  const connectWallet = async () => {
    if (typeof window.ethereum !== 'undefined') {
      try {
        // Request wallet connection
        const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });

        // Set the first connected account
        setAccount(accounts[0]);
        setError(null);  // Clear any previous error
      } catch (err) {
        // Handle error if user rejects the request or if there's another issue
        setError('User rejected the connection request or an error occurred.');
      }
    } else {
      setError('No Ethereum provider found. Please install MetaMask.');
    }
  };

  // Optionally: Detect account change or network change
  useEffect(() => {
    if (typeof window.ethereum !== 'undefined') {
      const handleAccountsChanged = (accounts) => {
        if (accounts.length > 0) {
          setAccount(accounts[0]);
        } else {
          setAccount(null); // Disconnected
        }
      };

      window.ethereum.on('accountsChanged', handleAccountsChanged);

      return () => {
        window.ethereum.removeListener('accountsChanged', handleAccountsChanged);
      };
    }
  }, []);

  return { account, connectWallet, error };
};

Le hook useConnect simplifie la connexion des portefeuilles, éliminant ainsi le besoin de réécrire le code sur chaque page. Bien qu'il ne soit pas strictement nécessaire pour ce projet, nous utiliserons le hook useEffect pour appeler la fonction retrieve, afin d'éviter les rendus inutiles.

useEffect(() => {
    retrieve().then((data) => {
      setNumber(formatEther(data))
    })
  }, [number, setNumber]);

Enfin, voici le code complet

import { useEffect, useState } from 'react'
import Morph from './assets/Morph.svg'
import viteLogo from '/vite.svg'
import './App.css'
import { createPublicClient, http, createWalletClient, custom, parseEther, formatEther } from 'viem'
import { morphHolesky } from 'viem/chains'
import { abi as ABI } from "./contract/abi";
import { useWallet } from './hooks/useConnect'

const client = createPublicClient({
  chain: morphHolesky,
  transport: http(),
})

const wallet_client = createWalletClient({
  chain: morphHolesky,
  transport: custom(window.ethereum)
})

function App() {
  const [number, setNumber] = useState(0);
  const { account, connectWallet, error } = useWallet();

  const store = async (number) => {
    const { request } = await client.simulateContract({
      address: '0x599d52A441Eef87f99241396ee14E4C9fFA78281',
      abi: ABI,
      functionName: 'store',
      args: [parseEther(number)],
      account
    });

    const hash = await wallet_client.writeContract(request);

    return hash;
  }
  const retrieve = async () => {
    const data = await client.readContract({
      address: '0x599d52A441Eef87f99241396ee14E4C9fFA78281',
      abi: ABI,
      functionName: 'retrieve',
      account
    });

    return data;
  }

  useEffect(() => {
    retrieve().then((data) => {
      setNumber(formatEther(data))
    })
  }, [number, setNumber]);
  return (
    <>
      <div className="logos">
        <a href="https://vite.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://morphl2.io" target="_blank">
          <img src={Morph} className="mlogo" alt="Vite logo" />
        </a>
      </div>
      <h1>Vite + Morph</h1>
      <button onClick={connectWallet}>
        {account? account : "Connect Wallet"}
      </button>
      <div className="card">
        <button onClick={() => {
          store(String(Number(number) + 1))
        }}>
          Number in storage is {number}
        </button>

      </div>
    </>
  )
}

export default App

Conclusion

Ce tutoriel vous a guidé pour créer une application web et la connecter à un Smart Contract déployé sur le testnet Morph Holesky en utilisant Viem. Vous pouvez cloner le code et expérimenter avec !

28
Subscribe to my newsletter

Read articles from Daniel Kambale directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Daniel Kambale
Daniel Kambale

Hello! I’m Daniel, a Web3 developer specializing in Solidity and smart contract development. My journey in the blockchain space is driven by a vision of a fully decentralized world, where technology empowers individuals and transforms industries. As an ambassador at Morph, I’m committed to fostering growth and innovation in this space, helping to shape a future that values transparency and security. Fluent in both English and French, I enjoy connecting with diverse communities and sharing my insights across languages. This is why you’ll find some of my articles in French, while others are in Swahili, as I believe knowledge should be accessible to all. I use my Hashnode blog to document my learning process, explore decentralized solutions, and share practical tutorials on Web3 development. Whether it's diving deep into Solidity, discussing the latest in blockchain, or exploring new tools, I’m passionate about contributing to a decentralized future and connecting with others who share this vision. Let’s build the future together!