README.md 18,2 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é

Abdoul Aziz BALDE's avatar
Abdoul Aziz BALDE a validé
**Metamask Flask** est une version expérimentale de **Metamask** destinée aux développeurs qui souhaitent explorer des fonctionnalités avancées, notamment celles liées aux Snaps. Cette section vous guide pour installer l'extension sur les navigateurs Chrome et Firefox. 

#### Étape 1 : Télécharger l'extension

1. **Sur Chrome** :  
   - Ouvrez le [Chrome Web Store](https://chrome.google.com/webstore).
   - Recherchez "Metamask Flask" dans la barre de recherche.  
   - Cliquez sur le résultat correspondant, puis sur le bouton **Ajouter à Chrome**.
   *voir capture d'ecran 1*
Abdoul Aziz BALDE's avatar
Abdoul Aziz BALDE a validé
![page d'installation metamask flask](assets/1.png)
    - Première ouverture
    Une fois l'installation terminée, une fenêtre s'ouvrira automatiquement avec le message d'avertissement Caution: Experimental Software. (Voir capture ci-dessous)
    ![page d'installation metamask flask](assets/3.png)
    Ce message rappelle que Metamask Flask est destiné aux développeurs et expérimentateurs. Si vous acceptez les termes, cliquez sur **J'accepte les risques** pour accéder à la page de création ou d'importation d'un portefeuille.
Abdoul Aziz BALDE's avatar
Abdoul Aziz BALDE a validé

2. **Sur Firefox** :  
   - Allez dans le [Firefox Add-ons Store](https://addons.mozilla.org).  
   - Recherchez "Metamask Flask".  
   - Cliquez sur le résultat correspondant, puis sur **Ajouter à Firefox**.


### Configuration

#### Étape 1 : Créer ou importer un portefeuille

1. **Créer un portefeuille** :  
Abdoul Aziz BALDE's avatar
Abdoul Aziz BALDE a validé
   - Une fois sur la page de creation d'un portefeuille comme illustré ci-dessous, cliquez sur **Créer un nouveau portefeuille**.
   ![creation compte metamask flask](assets/4.png)
   - Accepter les termes (Voir capture ci-dessous) pour passé à l'etape suivante
    ![creation compte etape 2 metamask flask](assets/5.png)
   - Ensuite définissez un mot de passe fort.
   ![creation mot de passe metamask flask](assets/6.png) puis cliquez sur **Créer un nouveau mot de passe**
   - Lorsqu'il vous est proposé de sauvegarder votre phrase de récupération (Seed Phrase), vous pouvez :
     - **Sécuriser mon portefeuille (recommandé)** : Notez soigneusement la phrase de récupération et conservez-la en lieu sûr (comme dans un coffre-fort).
     - **Me le rappeler plus tard (non recommandé)** : Cliquez sur cette option si vous souhaitez passer cette étape rapidement, en étant conscient des risques liés à la perte d'accès.
     ![phrase de recup metamask flask](assets/7.png) 
        - confirmer le choix de sauter le reglage des paramètre recommandés
     ![skip phrase de recup metamask flask](assets/8.png)
    - Esuite un message de rappel apparait, cliquer sur **teminé** (voir image).
    ![fin de creation de compte](assets/9.png)
    - Une fois cette étape terminée, la configuration de votre compte est achevée. Cliquez sur **Suivant** (voir images ci-dessous) pour passer à l'étape suivante, qui consistera à importer un portefeuille ou configurer le vôtre.
    ![fin de creation de compte](assets/10.png)
    ![fin de creation de compte](assets/11.png) 
Abdoul Aziz BALDE's avatar
Abdoul Aziz BALDE a validé

2. **Importer un portefeuille existant** :  
   - Cliquez sur **Import Wallet**.  
   - Saisissez votre Seed Phrase existante et suivez les instructions pour configurer votre portefeuille.

#### Étape 2 : Configurer le réseau blockchain

Abdoul Aziz BALDE's avatar
Abdoul Aziz BALDE a validé
1. Avant d'ajouter un reseau, cliquez sur le menu déroulant en haut à droite de l'interface de Metamask Flask puis sur paramettre (voir image suivante). 
![fin de creation de compte](assets/12.png) 
2. Sélectionnez **Paramètres avancés**.  
![fin de creation de compte](assets/13.png) 
puis afficher les reseaux de test
![fin de creation de compte](assets/14.png) 
3. Revenez à l'interfacede Metamask Flask  sélectionnez **Add Network** pour ajouter un réseau personnalisé.
![fin de creation de compte](assets/15.png)
4. Remplissez les informations requises, telles que :  
Abdoul Aziz BALDE's avatar
Abdoul Aziz BALDE a validé
   - Nom du réseau  
   - URL du RPC  
   - ID de la chaîne  
   - Symbole de la monnaie (facultatif)  
   - URL de l'explorateur (facultatif)  
4. Cliquez sur **Save**.

#### Étape 3 : Activer les Snaps (si applicable)

1. Accédez à la section **Snaps** dans l'interface de Metamask Flask.  
2. Suivez les instructions pour activer ou installer des Snaps compatibles avec vos besoins de développement.

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)