Je vais, dans ce tutoriel, vous montrer rapidement comment développer une dApp sur Horizen EON. Je vais créer un jeton ERC-20 dont le contrat embarque son propre faucet, TheFaucetCoin, dont le symbole sera TFC. Dans un second temps, je créérai une dApp permettant de réclamer des TFC en utilisant Ankr comme service RPC associé à RainbowKit et Wagmi pour la partie API de haut niveau.
Le Token TFC
Pour développer une dApp sur Horizen EON on a besoin d’un minimum de 2 composants, un smartcontract et une application pour l’utilisateur.
On attaque directement avec le code solidity du smart contracts du Token $TFC : The Faucet Coin
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract TheFaucetCoin is ERC20, ReentrancyGuard {
// Well ... Max Supply ...
uint maxSupply = 1000000000 * 10 ** 18;
uint dropAmount = 10 ** 18; // 1
// Store each address last airdrop time
mapping (address => uint) usersLastTime;
// Check if the user has claimed in the last ~24H
function canClaim() public view returns (bool) {
return (block.timestamp > usersLastTime[msg.sender] + 86400);
}
// Method to call to get a faucet drop
function useFaucet() external nonReentrant {
require (canClaim(), "Only once a day baby");
require (totalSupply() < maxSupply, "Max supply reached");
usersLastTime[msg.sender] = block.timestamp;
_mint(msg.sender, dropAmount);
}
constructor() ERC20("The Faucet coin", "TFC") {
// What else ?
}
}
On part d’un contrat ERC20 modèle OpenZeppelin de base dont on vide le constructeur.
On ajoute 3 variables :
maxSupply
contenant le nombre maximum de jetons pouvant être créés, ici un milliard.dropAmount
contenant le nombre de jetons octroyés par utilisation du faucet, ici 1.usersLastTime
contenant le moment de dernière utilisation du faucet par une adresse.
L’idée est donc de distribuer 1 TFC à chaque adresse utilisant le faucet en limitant à une utilisation par jour.
Le Faucet continue jusqu’à ce qu’on atteigne la supply totale maximum d’un milliard de tokens, soit environ 3 ans pour un million d’adresses.
J’utilise pour cela une méthode canClaim
qui vérifie si une adresse a le droit d’utiliser le Faucet.
Je ne me suis pas embêté et ai tapé ce code directement dans un nouveau fichier dans Remix, j’ai compilé et déployé sur Gobi et EON en utilisant Metamask comme environnement de déploiement. Clic droit sur le fichier source pour choisir Flatten et ainsi valider et publier le contrat sur les 2 blockchains testnet (gobi) et mainnet (eon).
La dApp
Autre composant indispensable pour développer une dApp sur Horizen EON, l’interface utilisateur.
On va construire la dApp étape par étape pour ne pas tout mélanger.
Application minimale et serveur de dév
On crée un nouveau répertoire thefaucetcoin
, et, à l’intérieur, une nouvelle app ReactJS. Je n’utilise n’utilise pas create-wagmi
afin que l’on ajoute nous mêmes les éléments un par un pour bien comprendre.
npx create-react-app .
Il faut ensuite ajouter wagmi et viem (et on ajoute typescript pour éviter des conflits avec une vieille dépendance de react-scripts, –force ou –legacy-peer-deps fonctionnent aussi. Il faudrait surtout que j’arrrête d’utiliser create-react-app
…) :
npm i wagmi viem typescript
On vide index.css et app.css et on repart avec un fichier App.js réduit à sa plus simple expression :
import './App.css';
function App() {
return (
<div className="App">
</div>
);
}
export default App;
On lance le serveur de développement avec npm start
pour constater que l’on obtient une page blanche (tout ça pour ça …).
Ajout de gobi aux chains de wagmi
Pour l’instant wagmi n’intègre pas gobi et eon dans les chains préconfigurées. Il faut donc que l’on ajoute un fichier de configuration pour chacune.
Voyons comment faire avec gobi, par exemple. J’ai donc créé un sous-répertoire utils
dans src
et y ai créé le fichier gobi.ts avec le contenu suivant :
import { Chain } from 'wagmi'
export const gobi = {
id: 1_663,
name: 'Gobi',
network: 'gobi',
nativeCurrency: {
decimals: 18,
name: 'ZEN',
symbol: 'ZEN',
},
rpcUrls: {
public: { http: ['https://gobi-testnet.horizenlabs.io/ethv1'] },
default: { http: ['https://gobi-testnet.horizenlabs.io/ethv1'] },
},
blockExplorers: {
etherscan: { name: 'Gobi Explorer', url: 'https://gobi-explorer.horizen.io/' },
default: { name: 'Gobi Explorer', url: 'https://gobi-explorer.horizen.io/' },
},
contracts: {
},
testnet: true,
} as const satisfies Chain
On l’importe en tête du fichier App.js :
import { gobi } from './utils/gobi.ts'
Configuration de wagmi
On peut ensuite passer à la configuration minimale de wagmi en appelant configureChains
et createConfig
et en wrappant notre app dans une balise WagmiConfig
:
import { WagmiConfig, createConfig, configureChains } from 'wagmi'
import { publicProvider } from 'wagmi/providers/public'
import { gobi } from './utils/gobi.ts'
import './App.css'
function App() {
const { publicClient } = configureChains(
[gobi],
[publicProvider()],
)
const config = createConfig({
autoConnect: true,
publicClient,
})
return (
<WagmiConfig config={config}>
<div className="App">
</div>
</WagmiConfig>
);
}
export default App;
Connexion au wallet
A ce stade on a toujours une page blanche, c’est frustrant. Ajoutons un composant Profile pour gérer la connexion.
function Profile() {
const { address, isConnected } = useAccount()
const { connect } = useConnect({
connector: new InjectedConnector(),
})
if (isConnected) {
return <div>
Connected to {address}
</div>
}
return <button onClick={() => connect()}>Connect Wallet</button>
}
Wagmi fonctionne à l’aide de hooks. On utilise ici useAccount
qui reflète l’état de connexion et l’adresse connectée, le cas échéant et useConnect
, qui permet d’interagir avec un Connecteur, ici, celui intégré au Browser (metamask/Brave wallet/Rabby/…).
On doit charger ces éléments avant de s’en servir, on ajoute donc en tête de fichier :
import { useAccount, useConnect } from 'wagmi'
import { InjectedConnector } from 'wagmi/connectors/injected'
Enfin, on insère le composant dans le corps de l’App :
<WagmiConfig config={config}>
<div className="App">
<Profile />
</div>
</WagmiConfig>
Et notre page n’est plus vide :
On veut Claim !!
Bien ! Maintenant qu’on sait se connecter, on va faire le composant Claim
qui permettra d’utiliser le faucet à TFC.
On va commencer par récupérer l’abi du smart contract déployé. L’ABI est généré lors de la compilation du contrat. Je l’ai placée dans le répertoire utils
, dans un fichier tfcAbi.json
. Il faut donc l’importer dans l’App.
import tfcContractAbi from './utils/tfcAbi.json'
On doit ensuite récupérer l’adresse de notre contrat pour s’en servir plus tard.
const tfcContractAddress = "0xd38D3BFcc7c29765F5c7569DFB931d851E3c7844"
Enfin, on ajoute quelques imports supplémentaires qui seront nécessaires à ce nouveau composant.
import { useState } from 'react';
import { useContractRead } from 'wagmi'
import { useContractWrite, usePrepareContractWrite } from 'wagmi'
Ok, voyons le code du composant Claim
:
function Claim(props) {
const [walletCanClaim, setCanClaim] = useState(false);
useContractRead({
address: tfcContractAddress,
abi: tfcContractAbi,
functionName: 'canClaim',
account: props.address,
onSuccess(data) {
setCanClaim(data);
},
})
const { config } = usePrepareContractWrite({
address: tfcContractAddress,
abi: tfcContractAbi,
functionName: 'useFaucet',
})
const { data, isLoading, isSuccess, write } = useContractWrite(config)
return <div>
<div>{ walletCanClaim ? "You can claim 1 $TFC" : "This wallet already claimed in the last 24h" }</div>
<div>
<button disabled={!walletCanClaim} onClick={() => write?.()}>
Claim my daily $TFC
</button>
</div>
{isLoading && <div>Check Wallet</div>}
{isSuccess && <div>Success ! <a href={"https://gobi-explorer.horizen.io/tx/" + data.hash}>Check Transaction</a></div>}
</div>
}
On déclare une variable d’état walletCanClaim
avec son setter setCanClaim
.
Vient ensuite l’appel de la fonction canClaim
du contrat. On fournit l’adresse du contrat, son ABI, la fonction appelée. On prend garde d’appeler avec l’adresse connectée pour éviter les soucis de cache. Enfin, en cas de bon déroulement, on stocke la valeur de retour dans la variable walletCanClaim
.
On prépare l’appel à la fonction activant le faucet useFaucet
. Et on a tout ce qu’il faut pour générer notre composant :
- Selon la valeur de
walletCanClaim
, on affiche un message pour indiquer l’éligibilité de ce wallet au drop quotidien. - On génère le bouton qui appele la fonction
useFaucet
mais, selon la valeur dewalletCanClaim
, on le désactive ou non. - Viennent ensuite 2 messages d’information affichés lorsque l’on attend une interaction de l’utilisateur dans son wallet et le message en cas de succès permettant l’affichage de la transaction dans l’explorer.
Le composant Claim
est défini. On peut le charger dans Profile :
if (isConnected) {
return <div>
Connected to {address}
<Claim address={address} />
</div>
}
On a vu comment développer une dApp sur Horizen EON et En l’état, la dApp est fonctionnelle. Mais il lui reste plusieurs problèmes ou améliorations possibles, que l’on va aborder dans la suite de l’article.
Sélectionner le bon réseau
Un sélecteur de réseau est bien évidemment pratique, sinon indispensable, pour s’assurer que les requêtes ne partent pas sur d’autres réseaux que gobi ou eon !
On va placer cet élément dans le composant Profile afin de bloquer l’App si on se trouve sur un réseau différent.
Le sélecteur utilisera les 2 hooks suivnats : useNetwork
et useSwitchNetwork
. On les importe donc depuis wagmi :
import { useNetwork, useSwitchNetwork } from 'wagmi'
On peut ensuite modifier le composant Profile :
function Profile() {
const { address, isConnected } = useAccount()
const { connect } = useConnect({
connector: new InjectedConnector(),
})
const { chain } = useNetwork()
const { switchNetwork } = useSwitchNetwork(gobi.id);
if (isConnected) {
if (chain.id === gobi.id) {
return <div>
Connected to {address}
<Claim address={address} />
</div>
}
return <button onClick={() => switchNetwork(gobi.id)}>Switch to Gobi Network</button>
}
return <button onClick={() => connect()}>Connect Wallet</button>
}
Et c’est tout. Une fois connecté, si vous n’êtes pas sur le bon réseau, un bouton vous propose de switcher. Et si vous quittez le réseau, l’interface de la dApp disparaît et réaffiche le bnouton de switch.
Utiliser un fournisseur de RPC : Ankr
On profite aussi de ce tutoriel pour explorer les outils pour le développement de dApp sur EON. On va utiliser Ankr pour l’interface RPC avec la blockchain.
Pourquoi ne pas utiliser les interfaces RPC publiques ? Parce que si une dApp génère trop de trafic sur les RPC publiques, il y a un risque qu’elle se retrouve rapidement limitée en nombre de requêtes acceptées, ce qui dégrade l’expérience utilisateur ou va même jusqu’à rendre inutilisable la dApp.
Accessoirement, les fournisseurs comme Ankr, Alchemy, ChainStack, Blockstream, etc. proposent des fonctionnalités supplémentaires et ne se limitent pas à la simple fourniture de d’interfaces RPC, notamment pour la manipulation de NFTs ou autres bonus.
On se rend sur le site d’Ankr pour y créer un compte gratuit. Ce dernier permet d’obtenir une adresse personnalisée pour l’interface et le suivi des requêtes qu’on y envoie.
— > https://www.ankr.com/rpc/horizen/
Une fois qu’on a notre endpoint personnalisé (on peut utiliser l’endpoint public d’Ankr pour les tests), on adapte la déclaration du client dans App()
.
const { publicClient } = configureChains(
[gobi],
[
jsonRpcProvider({
rpc: () => {
return {
http: "https://rpc.ankr.com/horizen_testnet_evm/14594...bbe3"
};
},
}),
],
)
A noter : Il est tout à fait possible de laisser en plus publicProvider()
, qui pourra servir de solution de repli « au cas où ».
A l’usage, on ne voit pas la différence … C’est frustrant, mais c’est bien.
Multiplier les wallets : RainbowKit
Pour enfin faire quelque chose qui se voit et améliorer l’interface utilisateur, on va s’attaquer à la partie connector de wagmi et utiliser RainbowKit, souvent utilisé sur les dApps en alternative à Web3Modal ou d’autres solutions multiwallet.
On commence par ajouter la bibliothèque de code nécessaire au projet (ajout de typescript pour la même raison que plus haut, create-react-app
n’est plus maintenu, il faut que je me mette à jour) :
npm install @rainbow-me/rainbowkit typescript
On a 2 imports à charger pour l’aspect de l’interface et les fonctionnalités et un de plus pour le bouton de connexion et son interface :
import '@rainbow-me/rainbowkit/styles.css'
import { getDefaultWallets, RainbowKitProvider } from '@rainbow-me/rainbowkit'
import { ConnectButton } from '@rainbow-me/rainbowkit';
RainbowKit, comme Web3Modal, utilise WalletConnect. Vous devrez donc créer un compte et un projet sur cloud.walletconnect.com pour définir un nom et une ID de projet qui nous servira dans la configuration. Tout cela est rapide et gratuit.
Bien, éditons à présent notre App()
:
function App() {
const { chains, publicClient } = configureChains(
[gobi],
[
jsonRpcProvider({
rpc: () => {
return {
http: "https://rpc.ankr.com/horizen_testnet_evm/1459...7bbe3"
};
},
}),
publicProvider(),
],
)
const { connectors } = getDefaultWallets({
appName: 'TheFaucetCoin',
projectId: '896cf........7ae4',
chains
});
const config = createConfig({
autoConnect: true,
connectors,
publicClient,
})
return (
<WagmiConfig config={config}>
<RainbowKitProvider chains={chains}>
<div className="App">
<Profile />
</div>
</RainbowKitProvider>
</WagmiConfig>
);
}
- Ligne 2, on récupère chains en plus en retour de l’appel à configureChains, pour s’en servir plus tard.
- Lignes 17 et 18, on initialise les connectors de RainbowKit avec le nom et l’ID récupérés sur notre projet déclaré sur cloud.walletconnect.com précédemment ainsi que la variable chains récupérée juste au dessus.
- Ligne 24, on ajoute les connectors à la config wagmi
- Lignes 30 et 34, on enserre notre App, dans le composant
RainbowKitProvider
.
Bon, après tout ça, on est prêts à remplacer notre bouton de connexion tout moche. On reprends le composant Profile :
function Profile() {
const { address, isConnected } = useAccount()
const { chain } = useNetwork()
const { switchNetwork } = useSwitchNetwork(gobi.id);
if (isConnected) {
if (chain.id === gobi.id) {
return <div>
Connected to {address}
<Claim address={address} />
</div>
}
return <button onClick={() => switchNetwork(gobi.id)}>Switch to Gobi Network</button>
}
return <ConnectButton />
}
- Ligne 3, l’appel à
useConnect
disparaît. C’est désormais le boulot de RainbowKit. - Ligne 18, on supprime le précédent bouton de connexion pour y mettre celui de RainbowKit.
Bonus : RainbowKit peut gérer tout seul le changement de réseau !
Constantes de configuration
Afin de faciliter la maintenance et la portabilité du code, on va en sortir toutes les constantes de configuration, nom de projet, id walletconnect, rpc, etc.
On va pour cela créer un fichier .env contenant les variables qu’on souhaite « déporter » du code de l’app. Les variables peuvent avoir le nom que l’on souhaite, mais ce dernier doit commencer par REACT_APP_
, sans quoi, il sera ignoré.
REACT_APP_TFC_CONTRACT_ADDRESS = "0xd38D3BFcc7c29765F5c7569DFB931d851E3c7844"
REACT_APP_BLOCK_EXPLORER_BASE_URL = "https://gobi-explorer.horizen.io/tx/"
REACT_APP_RPC_HTTP_URL = "https://rpc.ankr.com/horizen_testnet_evm/1459...7bbe3"
REACT_APP_WC_PROJECT_NAME = "TheFaucetCoin"
REACT_APP_WC_PROJECT_ID = "896c...7ae4"
On peut ensuite les utiliser dans le code en utilisant process.env.NOM_DE_VARIABLE
:
const tfcContractAddress = process.env.REACT_APP_TFC_CONTRACT_ADDRESS
...
<a href={process.env.REACT_APP_BLOCK_EXPLORER_BASE_URL + data.hash}>Check Transaction</a>
...
const { chains, publicClient } = configureChains(
[gobi],
[
jsonRpcProvider({
rpc: () => {
return {
http: process.env.REACT_APP_RPC_HTTP_URL
};
},
}),
publicProvider(),
],
)
...
const { connectors } = getDefaultWallets({
appName: process.env.REACT_APP_WC_PROJECT_NAME,
projectId: process.env.REACT_APP_WC_PROJECT_ID,
chains
});
...
Pour que les modifications dans le fichier .env
soient prises en compte, il faut relancer le serveur de développement.
Conclusion
Voila ce tutoriel « Développer une dApp sur Horizen EON » est maintenant terminé. Si tu as eu un souci à le suivre, tu peux me contacter sur twitter (euh X, pardon) et sur le Discord Horizen !
Liens
- Portail Horizen EON : https://eon.horizen.io/
- Wagmi : https://wagmi.sh/
- Ankr : https://www.ankr.com/
- RainbowKit : https://www.rainbowkit.com/
- Plus de détails sur les variables d’environnement : Adding custom environment variables
- The Faucet Coin : https://thefaucetcoin.mescryptos.fr/
- Plus de contenu Horizen sur Mes Cryptos : https://mescryptos.fr/tag/horizen/