README.md 14 ko
Newer Older
Maxence Lambard's avatar
Maxence Lambard a validé
## Installation et configuration de Metamask Flask
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
### Installation
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
### Configuration
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
Metamask Flask est une version de Metamask. Ils ont le même fonctionnement mais il est plus adapté au développement.
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
SI vous avez fait le choix d'utiliser Hardhat, il faut modifier la propriété `chainId` de Hardhat afin de simplifier la configuration de Metamask Flask.
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
```ts
require("@nomicfoundation/hardhat-toolbox");
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.24",
  networks: {
    hardhat: {
      chainId: 1337,
    },
  },
};
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
```

## Création de l'application avec Vite

On initialise l'application à l'aide de Vite. Ici, on utilise la template `react-ts` :

```bash
npm create vite@latest dApp -- --template react-ts
```

Allons dans la racine de l'application décentralisée :

```bash
cd dApp
```

Faisons un peu de nettoyage :

```bash
rm src/index.css src/App.css
```

Installons les dépendences :

```bash
npm install
```

Modifions le fichier `main.tsx` :

```tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)
```

Modifions le fichier `App.tsx` :

```tsx
export default function App() {
    return (
        <></>
    );
}
```

Nous pouvons maintenant lancer notre serveur de développement :

```bash
npm run dev
```

Une page blanche devrait s'afficher.
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
## Gestion du portefeuille avec Wagmi

Wagmi est une bibliothèque permettant de gérer des portefeuilles, signer des messages, envoyer des transactions...

```bash
npm install wagmi viem@2.x @tanstack/react-query
Maxence Lambard's avatar
Maxence Lambard a validé
```
Maxence Lambard's avatar
Maxence Lambard a validé

Ajoutons un fichier `config.ts` dans le dossier `src` nous permettant de configurer notre réseau Ethereum local émulé par Hardhat : 

```bash
import { createConfig, http } from 'wagmi'
import { localhost } from 'wagmi/chains'

export const config = createConfig({
    chains: [localhost],
    transports: {
        [localhost.id]: http(),
    },
})
Maxence Lambard's avatar
Maxence Lambard a validé
```

Maxence Lambard's avatar
Maxence Lambard a validé
Maintenant, on modifie le composant `App` pour y intégrer le Wagmi :
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
```tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { WagmiProvider } from 'wagmi'
import { config } from './config'
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
const queryClient = new QueryClient()
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
export default function App() {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
      </QueryClientProvider>
    </WagmiProvider>
  )
}
```
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
Ensuite, on ajoute 3 fichiers au dossier `src/components` :
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
```bash
mkdir src/components && touch src/components/Account.tsx src/components/ConnectWallet.tsx src/components/WalletOptions.tsx
```
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
Les composants permettent de :
- `Account` : Affiche les informations du portefeuille courant.
- `WalletOptions` : Affiche les boutons permettant de se connecter aux différents portefeuille installés sur le navigateur. 
- `ConnectWallet` : Permet d'alterner entre les composants `Account` et `WalletOptions`.

Le code du composant `Account` :

```tsx
import { useAccount, useDisconnect, useEnsAvatar, useEnsName } from 'wagmi'

export function Account() {
  const { address } = useAccount()
  const { disconnect } = useDisconnect()
  const { data: ensName } = useEnsName({ address })
  const { data: ensAvatar } = useEnsAvatar({ name: ensName! })

  return (
    <div>
      {ensAvatar && <img alt="ENS Avatar" src={ensAvatar} />}
      {address && <div>{ensName ? `${ensName} (${address})` : address}</div>}
      <button onClick={() => disconnect()}>Disconnect</button>
    </div>
  )
}
```
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
Le code du composant `WalletOptions` :
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
```tsx
import { useConnect } from 'wagmi'
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
export function WalletOptions() {
  const { connectors, connect } = useConnect()
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
  return connectors.map((connector) => (
    <button key={connector.uid} onClick={() => connect({ connector })}>
      {connector.name}
    </button>
  ))
}
```
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
Pour finir, le code du composant `ConnectWallet` :
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
```tsx
import { useAccount } from "wagmi"
import { Account } from "./Account"
import { WalletOptions } from "./WalletOptions"
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
export default function ConnectWallet() {
    const { isConnected } = useAccount()
    if (isConnected) return <Account />
    return <WalletOptions />
}
```
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
On peut maintenant modifier le composant `App` pour y ajouter le composant `ConnectWallet` :

```tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { WagmiProvider } from 'wagmi'
import ConnectWallet from './components/ConnectWallet'
import { config } from './config'

const queryClient = new QueryClient()

export default function App() {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <ConnectWallet />
      </QueryClientProvider>
    </WagmiProvider>
  )
}
```
Maxence Lambard's avatar
Maxence Lambard a validé
## Interaction avec un Smart Contract
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
Nous allons réutiliser notre Smart Contract que nous avons déployé.
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
Dans un premier temps, nous allons structurer. Ajoutons 3 composants et un fichier de configuration au dossier `src/components/IncredibleStorage` :
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
```bash
mkdir src/components/IncredibleStorage && \
touch src/components/IncredibleStorage/IncredibleStorage.tsx \
src/components/IncredibleStorage/IncredibleStorageInfo.tsx \
src/components/IncredibleStorage/IncredibleStorageForm.tsx \
src/components/IncredibleStorage/IncredibleStorageConfig.ts
```
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
Ils vont nous aider à intégrer un formulaire nous permettant de modifier notre Smart Contract via la fonction `set`. De plus, le composant `IncredibleStorageInfo` affichera les informations en temps réel. Le fichier de configuration stockera 2 variables : 
- `abi` : La structure du Smart Contract.
- `address` : L'adresse du Smart Contract déployé.

Avant de copier le code, allons chercher l'abi et l'adresse du Smart Contract déployé. Dans votre projet Hardhat, vous trouverez l'abi au chemin suivant `artifacts/contracts/IncredibleStorage.sol/IncredibleStorage.json`. Pour ce qui est de l'adresse, elle est affichée lors du [déploiement](https://git.litislab.fr/blockchain/documentation/outils-ethereum/-/wikis/Manipulation-des-Smart-Contracts#d%C3%A9ploiement-du-smart-contract). 

Ensuite, voilà à quoi le fichier de configuration `IncredibleStorageConfig.ts` devrait ressembler :
```ts
const abi = [
    {
        "inputs": [
            {
                "internalType": "uint256",
                "name": "_awesomeUInt",
                "type": "uint256"
            }
        ],
        "stateMutability": "nonpayable",
        "type": "constructor"
    },
    {
        "inputs": [],
        "name": "get",
        "outputs": [
            {
                "internalType": "uint256",
                "name": "",
                "type": "uint256"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [
            {
                "internalType": "uint256",
                "name": "x",
                "type": "uint256"
            }
        ],
        "name": "set",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    }
] as const

const address = "0x5FbDB2315678afecb367f032d93F642f64180aa3" as const // A MODIFIER

export const IncredibleStorageConfig = {
    address,
    abi
}
```
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
Ajouter le code suivante au composant `IncredibleStorageInfo` :
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
```tsx
interface IncredibleStorageInfoProps {
    awesomeUInt: bigint | undefined
}

export function IncredibleStorageInfo(props: IncredibleStorageInfoProps) {
    return (
        <div>
            <p>Value: {props.awesomeUInt?.toString()}</p>
        </div>
    )
}
```

Ajoutons maintenant le code du composant `IncredibleStorage` qui est le composant principal :

```tsx
import { useAccount, useReadContract } from "wagmi";
import { IncredibleStorageForm } from "./IncredibleStorageForm";
import { IncredibleStorageConfig } from "./IncredibleStorageConfig";
import { IncredibleStorageInfo } from "./IncredibleStorageInfo";

export function IncredibleStorage() {
    const { isConnected } = useAccount()

    const { data: awesomeUInt } = useReadContract({
        address: IncredibleStorageConfig.address,
        abi: IncredibleStorageConfig.abi,
        functionName: 'get',
        args: [],
    })

    return (
        <>
            {isConnected && (
                <div>
                    <IncredibleStorageForm />
                    <IncredibleStorageInfo awesomeUInt={awesomeUInt} />
                </div>
            )}
        </>
    )
}
```

Rajoutons le formulaire au composant `IncredibleStorageForm`. On affiche le hash de la transaction afin d'avoir une preuve de l'écriture sur le noeud blockchain Hardhat :

```tsx
import { useAccount, useWriteContract } from 'wagmi'
import { IncredibleStorageConfig } from './IncredibleStorageConfig'

export function IncredibleStorageForm() {
    const { isConnected } = useAccount()
    const { data: hash, isPending, writeContract } = useWriteContract()


    async function submit(e: React.FormEvent<HTMLFormElement>) {
        e.preventDefault()
        const formData = new FormData(e.target as HTMLFormElement)
        const awesomeUInt = formData.get('_awesomeUInt') as string

        writeContract({
            address: IncredibleStorageConfig.address,
            abi: IncredibleStorageConfig.abi,
            functionName: 'set',
            args: [BigInt(awesomeUInt)],
        })
    }

    return (
        <>
            {isConnected && (
                <form onSubmit={submit}>
                    <input name="_awesomeUInt" placeholder="76" type="number" required />
                    <button
                        disabled={isPending}
                        type="submit"
                    >
                        Update IncredibleStorage
                        {isPending ? ' Confirming...' : ''}
                    </button>
                    {hash && <div>Transaction Hash: {hash}</div>}

                </form>
            )}
        </>
    )
}
```

Il ne faut pas oublier de rajouter le composant `IncredibleStorage` au composant `App` :

```tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { WagmiProvider } from 'wagmi'
import ConnectWallet from './components/ConnectWallet'
import { config } from './config'
import { IncredibleStorage } from './components/IncredibleStorage/IncredibleStorage'

const queryClient = new QueryClient()

export default function App() {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <ConnectWallet />
        <IncredibleStorage/>
      </QueryClientProvider>
    </WagmiProvider>
  )
}
```

Ce qui devrait nous donner :
![Figure 1](https://git.litislab.fr/blockchain/documentation/outils-ethereum/-/raw/main/assets/figure-1.gif)

Nous avons un problème, lorsque nous mettons à jour la valeur, `IncredibleStorageInfo` ne se met pas à jour. Il faut utiliser la propriété `refetch` du hook `useReadContract`. On ajoute à `IncredibleStorageForm` une propriété refetchInfo pour déclencher le rafraichissement du hook lorsque le Smart Contract est mis à jour :

```tsx
import { useAccount, useReadContract } from "wagmi";
import { IncredibleStorageForm } from "./IncredibleStorageForm";
import { IncredibleStorageConfig } from "./IncredibleStorageConfig";
import { IncredibleStorageInfo } from "./IncredibleStorageInfo";

export function IncredibleStorage() {
    const { isConnected } = useAccount()

    const { data: awesomeUInt, refetch: refetchInfo } = useReadContract({
        address: IncredibleStorageConfig.address,
        abi: IncredibleStorageConfig.abi,
        functionName: 'get',
        args: [],
    })

    return (
        <>
            {isConnected && (
                <div>
                    <IncredibleStorageForm refetchInfo={refetchInfo} />
                    <IncredibleStorageInfo awesomeUInt={awesomeUInt} />
                </div>
            )}
        </>
    )
}
```

Pour pouvoir faire en sorte que cela fonctionne, nous devons modifier `IncredibleStorageForm` en lui ajoutant une interface `IncredibleStorageFormProps` nous permettant d'ajouter la propriété `refetchInfo`. On ajoute les propriétés `isSuccess` et `isLoading` pour voir la progression de la modification du Smart Contract (en plus de quelques balise HTML pour faire bien).

```tsx
import { useAccount, useWaitForTransactionReceipt, useWriteContract } from 'wagmi'
import { IncredibleStorageConfig } from './IncredibleStorageConfig'
import { useEffect } from 'react'

interface IncredibleStorageFormProps {
    refetchInfo: any
}

export function IncredibleStorageForm(props: IncredibleStorageFormProps) {
    const { isConnected } = useAccount()
    const { data: hash, isPending, writeContract } = useWriteContract()
    const { isLoading: isConfirming, isSuccess: isConfirmed } =
        useWaitForTransactionReceipt({
            hash,
        })

    async function submit(e: React.FormEvent<HTMLFormElement>) {
        e.preventDefault()
        const formData = new FormData(e.target as HTMLFormElement)
        const awesomeUInt = formData.get('_awesomeUInt') as string

        writeContract({
            address: IncredibleStorageConfig.address,
            abi: IncredibleStorageConfig.abi,
            functionName: 'set',
            args: [BigInt(awesomeUInt)],
        })
    }

    useEffect(() => {
        props.refetchInfo()
    }, [isConfirmed])

    return (
        <>
            {isConnected && (
                <form onSubmit={submit}>
                    <input name="_awesomeUInt" placeholder="76" type="number" required />
                    <button
                        disabled={isPending}
                        type="submit"
                    >
                        Update IncredibleStorage
                        {isPending ? ' Confirming...' : ''}
                    </button>
                    {hash && <div>Transaction Hash: {hash}</div>}
                    {isConfirming && <div>Waiting for confirmation...</div>}
                    {isConfirmed && <div>Transaction confirmed.</div>}
                </form>
            )}
        </>
    )
}
```
Maxence Lambard's avatar
Maxence Lambard a validé

Maxence Lambard's avatar
Maxence Lambard a validé
Ce qui devrait nous donner : 
![Figure 2](https://git.litislab.fr/blockchain/documentation/outils-ethereum/-/raw/main/assets/figure-2.gif)