blockchain

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

Mettre en place un projet blockchain Ethereum avec Hardhat

Qu’est ce que Hardhat?

Hardhat est un ensemble logiciciel pour vous aider à développer un projet blockchain, tout comme un Laragon ou MAMP va vous simuler un Internet local, Hardhat va vous simuler une blockchain locale, élément nécessaire pour faire marcher un smart contract.

Hardhat est à privilégier pour tout nouveau projet blockchain, car Truffle n’est plus maintenu, il est possible que beaucoup de projet legacy utilisent encore Truffle cependant.

Configuration de VSCode pour un projet blockchain

Installation du plugin Remix Light et Solidity

Le plugin Solidity permet le syntax highlighting et l’intégration avec le compilateur, Solidity est un langage compilé.

Remix Light permet de tester et débugger.

Structure du projet blockchain

Créez un répertoire my-solidity-project entrez dedans. La strucutre sera comme ci-dessous

my-solidity-project/
├── contracts/         # Your smart contracts go here
├── scripts/           # Deployment and interaction scripts
├── test/              # Test files for your contracts
├── hardhat.config.js  # Hardhat configuration
└── package.json       # Project dependencies

Nous allons avoir besoin de NPM pour développer un projet blockchain.

// initialisation d'une projet nodeJS avec le flag -y pour répondre oui à toutes les questions

npm init -y

Installations du framework Hardhat

npm install --save-dev hardhat @nomicfoundation/hardhat-ethers ethers dotenv @nomicfoundation/hardhat-toolbox


// initialisation du framework

npx hardhat  (et suivre les instructions  /!\ c'est NPX et non NPM)
 
// on va choisir create a javascript project (mais il faut savoir que Typescript est très populaire)

Création du premier contrat

Un contrat est simplement un fichier en langage Solidity

Codage d’un contrat en Solidity MyToken

Rappel vous devez avoir la structure suivante:
my-solidity-project/
├── contracts/         # Your smart contracts go here
├── scripts/           # Deployment and interaction scripts
├── test/              # Test files for your contracts
├── hardhat.config.js  # Hardhat configuration
└── package.json       # Project dependencies
// le fichier se trouve dans contracts/MyToken.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyToken is ERC20, Ownable {
    constructor(
        address initialOwner
    ) ERC20("MyToken", "MTK") Ownable(initialOwner) {
        _mint(msg.sender, 1000000 * 10 ** decimals());
    }

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }
}

Le script ci-dessus fait appel à OpenZeppelin, on va installer ce dernier

npm install @openzeppelin/contracts

Création d’un script de déploiement

script/deploy.js

const hre = require("hardhat");

async function main() {
    const [deployer] = await ethers.getSigners();
    console.log("Deploying contracts with the account:", deployer.address);

    const MyToken = await ethers.getContractFactory("MyToken");
    const myToken = await MyToken.deploy(deployer.address);

    await myToken.waitForDeployment();
    console.log("MyToken deployed to:", await myToken.getAddress());
}

main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error(error);
        process.exit(1);
    });

Création d’un fichier de test

// tests/myToken.js

const { expect } = require("chai");

describe("MyToken", function () {
  let myToken;
  const initialMessage = "Hello, Hardhat!";

  beforeEach(async function () {
    const MyToken = await ethers.getContractFactory("MyToken");
    myToken = await MyToken.deploy(initialMessage);
  });

  it("Should return the initial message", async function () {
    expect(await myToken.getMessage()).to.equal(initialMessage);
  });

  it("Should set a new message", async function () {
    const newMessage = "New message";
    await myToken.setMessage(newMessage);
    expect(await myToken.getMessage()).to.equal(newMessage);
  });
});

Compilation du contrat

// compilation des contracts
npx hardhat compile

// les fichiers compilés sont dans artifacts/contracts

// jouer les test
npx hardhat test


Si vous avez une erreur essayez la commande suivante:
npm cache clean --force

Démarrer la blockchain locale

Tout comme en Web2 on a un internet local avec un serveur web, nous allons lancer une blockchain locale. Ensuite on va pouvoir déployer notre smart contract.

// démarrer le noeud de blockchain local
npx hardhat node

// cette commande va afficher les 20 wallets créés par défaut

Déployer le contrat

// Déployer le contract dans le noeud local
npx hardhat run scripts/deploy.js --network localhost

Deploying contracts with the account: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
MyToken deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3

Cette étape est importante car vous allez voir l’adresse de votre smart contract

En résumé :

  • compiler le smart contract
  • Tester le smart contract
  • démarrer un noeud
  • déployer les smart contrat sur le noeud
  • interagir avec le smart contract

Et voilà !

Interagir avec un smart contract qu’on a déployé

Démarrez plusieurs terminaux pour avoir la vue sur tout les scripts du projet. Nous allons interagir avec le smart contract. Pas question encore de le faire avec un wallet du genre Metamask, il y a plusieurs façons d’interagir avec un smart contract.

Interaction avec une console Hardhat

//démarre une console Hardhat et la branche sur le réseau localhost

npx hardhat console --network localhost

Ensuite il y a une invite de commande. Vous allez mettre ce code dans l’invite de commande. Il n’y a pas de fichier javascript, on interagit en ligne de commande. Pour utiliser les exemples, il faut remplacer l’adresse du smart contract par celui que vous obtenez au lancement.

// Get the contract factory
const MyToken = await ethers.getContractFactory("MyToken");

// Connect to your deployed contract (replace with your actual contract address)
const myToken = await MyToken.attach("0x5FbDB2315678afecb367f032d93F642f64180aa3");

// Call read functions
const name = await myToken.name();
console.log("Token name:", name);

const totalSupply = await myToken.totalSupply();
console.log("Total supply:", totalSupply.toString());

// Call write functions (transactions)
const [owner, addr1] = await ethers.getSigners();
const mintTx = await myToken.mint(addr1.address, ethers.parseEther("100"));
await mintTx.wait();
console.log("Minted 100 tokens to:", addr1.address);

// Check balance
const balance = await myToken.balanceOf(addr1.address);
console.log("Balance:", ethers.formatEther(balance));

Je vous invite à jouer avec les lignes de commande pour afficher les contenus des variables par exemple.

Interaction avec un script nodeJS

//  interact.js

const hre = require("hardhat");

async function main() {
  // Replace with your deployed contract address
  const contractAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3";
  
  const MyToken = await ethers.getContractFactory("MyToken");
  const myToken = MyToken.attach(contractAddress);
  
  console.log("Token name:", await myToken.name());
  console.log("Token symbol:", await myToken.symbol());
  
  const [owner, addr1] = await ethers.getSigners();
  console.log("Owner address:", owner.address);
  
  // Get owner balance
  const ownerBalance = await myToken.balanceOf(owner.address);
  console.log("Owner balance:", ethers.formatEther(ownerBalance));
  
  // Mint tokens to addr1
  console.log("Minting 100 tokens to:", addr1.address);
  const mintTx = await myToken.mint(addr1.address, ethers.parseEther("100"));
  await mintTx.wait();
  
  // Get addr1 balance
  const addr1Balance = await myToken.balanceOf(addr1.address);
  console.log("Address 1 balance:", ethers.formatEther(addr1Balance));
}

main()
  .then(() => process.exit(0))
  .catch(error => {
    console.error(error);
    process.exit(1);
  });

Pour faire tourner le script vous devez l’invoquer avec Hardhat

npx hardhat run scripts/interact.js --network localhost

Quoi d’autres après?

Il serait intéressant de faire une application ReactJS pour interagir avec le smart contract.

Je participe à un projet Hyperledger

Cela fait pas mal de temps que je voulais m’investir dans un projet blockchain, à défaut de blockchain publique, je vais travailler sur une blockchain privée, et c’est sur Hyperledger.

Le type de projet de tokenization immobilière, thème très en vogue actuellement. Le but est de permettre une plus grande liquidité dans l’immobilier et de dépoussiérer les pratiques en vigueur.

RealtyKey.io est la plateforme web2 pour aborder le web3.

Principe de RealtyKey

Le principe est un principe d’investissement. Jusque là rien de différent, là où ça commence à se différencier de la finance traditionnelle, c’est que vous achetez un certificat d’investissement, appelé NFT et ce après avoir créé un compte (vous obtenez un AccountKey), vous détenez un NFT InvestKey prouvant que vous êtes inscrit . Ce dernier NFT vous permet de minter deux autres types de NFT, les Incomekey qui vous permettent de toucher un loyer du bien dans lequel vous avez investi, et les RealtyKey qui vous permettent d’habiter quelques temps dans un bien dans lequel vous avez investi.

La stack technique

La blockchain Hyperledger est contenue dans un Kubernetes, un orchestrateur de conteneurs Dockers. Le langage de développement de la blockchain, celui utilisé pour écrire les smart contracts appelé chaincode dans Hyperledger, est le langage Go, assez facile à appréhender.

Hyperledger expose un webservice directement consommable via Postman par exemple.

C’est assez différent de ce que je connais sur les blockchains publiques, notamment Ethereum et autres EVM compatibles. Le langage pour faire les smart contracts pour les chaine EVM est Solidity , que certains disent assez proche de Javascript, ce que personnellement je ne trouve pas, c’est assez unique.

Les différences entre une blockchain privée et une blockchain publique

Le choix d’une blockchain privée est motivée par les raison suivantes (non exhaustives)

  • pas de notion de consensus
  • milieu confiné à priori non sensible aux hacks
  • grand TPS
  • beaucoup plus sécurisée

Pourquoi utiliser la blockchain pour investir de façon fractionnée dans l’immobilier?

La blockchain permet de tout tracer sans pouvoir de falsification. Le fait de tokéniser rend l’actif plus liquide, et améliore la facilité de trouver une contrepartie quand on veut vendre.

Meilleure liquidité

Le ticket d’entrée bas permet à tout le monde de participer à la possession d’un bien. Vous n’avez pas à passer les étapes du notaire, DPE, etc. Tout est fait, vous n’avez qu’à entrer dans un investissement après avoir obtenu votre account key. L’opération ne prend que 15 minutes.

Meilleure contrepartie

L’apport de liquidités et l’ouverture à un marché financier cryptonatif plus vaste permettra une plus grande participation d’acteurs (idéalement particuliers comme institutionnels), concrètement si vous ne voulez plus investir dans un bien, où que vous voulez arbitrer vers un autre bien, et que vous voulez vendre vos parts, vous trouverez plus facilement un acheteur pour vos parts.

Ceci est à mettre en opposition avec un produit comme la SCPI, où il est notoire que les conditions de sortie sont plus délicates et de loin (clause de sortie, illiquidité du produit).

Se familiariser avec l’API Binance

Maintes fois j’ai tâté de l’API Binance, entrecoupé de période d’inactivité, et quand je revenais, je devais tout réapprendre, ayant tous oublié…

Ce post est une tentative de mémorisation de cette API, mes besoins pour le moment concernent l’order book et les kline (chandelier japonais).

La page d’entrée de la documentation API Binance.

Order book

L’order book est le livre des ordres de bourse à cours limité, ce sont des ordres en attente d’exécution, à la différence des ordres market, que vous ne verrez pas car ils sont passés immédiatement.

Les données vous permettent de tracer la profondeur de marché, de voir la densité des ordres en fonction du cours d’exécution

Quotation en temps réel à différents timeframes

Cette fonctionnalité est primordiale pour tracer les cours des crypto, on a besoin en version OHLC, pour tracer les bougies et faire de l’analyse technique dessus.

logo ethereum

Requêter Uniswap avec TheGraph

TheGraph s’apuie sur la technologie GraphQL de Faceboo, bien connu des développeurs web, pour requêter Uniswap, à la manière d’une webservice REST.

Il faut se faire la main avec la syntaxe de GraphQL, mais elle est assez intuitive.

Pour ce faire nous allons aller sur le site de TheGraph, et directement sur l’interface de requêtage vers Uniswap V3.

Former une requête simple vers Uniswap V3

Dans la fenêtre, vous allez coller la requêt suivante :

{
  tokens {
    symbol
    name
    decimals
  }
}

Cette requpete va lister tous les tokens, sans filtre.

Filtre avec une crypto spécifique

{
  tokens (where:{name:"Neos Credits"},first:1){
    symbol
    name
    decimals
  }
}
La sandbox de TheGraph

Les entités d’Uniswap V3

La troisième colonne liste les entités que vous pouvez requêter. On retrouve l’entité Token, en cliquant dessus vous aurez le schéma de cette entité.

logo ethereum

Comment staker ses polkadots

Installer

  • Installer l’extension du wallet polkadot.js
  • créer deux accounts, un polkadot stash (le stash est comparable à un cold wallet) et un polkadot controller (comparable à un hot wallet). Ce sotn juste des noms, ils sont identiques dans leur nature, c’est après leur association dans l’interface de polkadot que leur rôle va diverger
  • virer les coin polkadot dans un des accounts, de préférence celui avec le stash.
  • Aller à la page principale de polkadot, aller dans Network > Staking, puis dans Account actions, cliquer sur le signe +à côté de Nominator, il vous faut un être un nominator avant de pouvoir nommer un validator. Nommez 16 validateurs.

Etudiez bien les profil des validateurs avant de le nommer. Choisissez ceux qui sont identifiés, ayant un bon historique (pas de slashing).

Retour en haut