Construire une DApp de marché de prédiction avec Pyth Oracle et le kit de démarrage Morph
Ah, les marchés de prédiction. La nouvelle tendance. De la prédiction des résultats des élections à la prévision des prix des cryptomonnaies, ces plateformes de prédiction décentralisées captivent l’imagination des traders et des développeurs. Aujourd’hui, je vais vous guider pour créer votre propre DApp de marché de prédiction en utilisant Pyth Oracle.
Les oracles servent de passerelles entre la blockchain et les sources de données externes, apportant des informations cruciales à vos contrats intelligents. Ils permettent à votre DApp de réagir aux événements du monde réel et de prendre des décisions basées sur des données précises et opportunes.
Pyth Oracle
Le réseau Pyth est un oracle financier de première partie, conçu pour publier en continu des données du monde réel en chaîne dans un environnement résistant aux manipulations, décentralisé et auto-suffisant. Il connecte les détenteurs de données de marché à des applications sur de multiples blockchains, avec des données fournies par plus de 100 éditeurs de première partie, y compris des bourses et des entreprises de création de marché.
Pyth propose plus de 200 flux de prix, incluant les cryptos, les actions, les devises étrangères (FX) et les métaux, mis à jour deux fois par seconde sans restrictions d'accès. Le réseau a sécurisé plus de 2 milliards de dollars en valeur totale et prend en charge des volumes de trading importants, ce qui en fait un oracle fiable pour plus de 350 protocoles sur plus de 55 blockchains. Pour plus de détails, consultez le réseau Pyth (Pyth network).
Que construisons-nous ?
Si vous avez suivi jusqu’ici, vous avez sûrement déjà compris. Nous allons construire une DApp de marché de prédiction qui nous permettra de prédire les prix de n’importe quel jeton crypto en utilisant Solidity (Foundry), Pyth Oracle pour récupérer les prix, Typescript et Nextjs pour notre interface.
Fonctionnalités
Notre DApp de marché de prédiction nous permettra de :
Créer des marchés : cela pourrait concerner n’importe quel crypto, action, devise ou même matière première.
Acheter des actions : les utilisateurs pourront acheter des actions pour appuyer leur prédiction (Oui ou Non).
Résoudre les marchés : une fois que l'échéance est passée, la DApp résout le marché en utilisant Pyth Oracle pour déterminer l'issue et les gagnants.
Réclamer : les gagnants pourront réclamer leurs gains pour avoir fait des prédictions correctes.
Passons à la construction !
Configuration de notre environnement
Nous allons commencer en utilisant le kit de démarrage Morph. Ce kit de démarrage est conçu pour simplifier la configuration de votre environnement pour construire et déployer sur Morph.
Dans votre terminal, exécutez la commande ci-dessous et choisissez le nom de votre DApp ou appuyez sur Entrée pour sélectionner le nom par défaut.
bashCopier le codenpx @morphl2/create-morph-app@latest
Après l’installation réussie, vous verrez quelques options de configuration.
Configuration de Foundry et création des contrats intelligents
cd my-morph-app
Nous utiliserons Foundry pour écrire et déployer nos contrats intelligents. Naviguez dans le répertoire contracts/foundry
, et exécutez les commandes suivantes pour renommer le fichier env.example
en .env
et pour compiler les contrats modèles.
cp .env.example .env
forge build
.env
Avant de commencer, nous devons configurer nos variables d'environnement. Collez votre clé privée dans le fichier .env
à la racine de votre projet.
PRIVATE_KEY=votre-clé-privée
RPC_URL=https://rpc-quicknode-holesky.morphl2.io
Token.sol
Nous allons maintenant écrire nos contrats intelligents. Dans votre dossier src
, supprimez le fichier modèle (MyToken.sol
) et créez un nouveau fichier appelé Token.sol
. Cela servira de monnaie pour notre DApp de prédiction. Collez le code ci-dessous :
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract OkidoToken is ERC20 {
constructor(uint256 initialSupply) ERC20("Okido Token", "OKD") {
_mint(msg.sender, initialSupply);
}
}
Market.sol
Le contrat Market
est l’implémentation de la DApp de marché de prédiction. Toujours dans votre dossier src
, créez un autre fichier appelé Market.sol
et collez le code de ce lien.
Dans votre terminal (assurez-vous d'être dans le répertoire foundry
), exécutez la commande ci-dessous pour installer Pyth dans notre projet Foundry :
forge install pyth-network/pyth-sdk-solidity@v2.2.0 --no-git --no-commit
Ensuite, nous ajoutons Pyth à nos remappings dans le fichier foundry.toml. Collez cette ligne dans le tableau des remappings de votre fichier foundry.toml.
"@pythnetwork/=lib/pyth-sdk-solidity/"
Votre fichier foundry.toml devrait ressembler à ceci.
De plus, les avertissements dans le contrat market devraient avoir disparu et votre fichier Market.sol devrait ressembler à l'exemple ci-dessous.
Aperçu du Contrat
Variables clés
IERC20 public paymentToken; // The token used for betting
IPyth public pythOracle; // Pyth price feed oracle
uint256 public marketCount; // Total number of markets created
uint256 public constant FEE_PERCENTAGE = 1; // 1% fee on transactions
fonction createMarket
function createMarket(
string memory cryptoPair,
uint256 strikePrice,
uint256 endTime,
uint256 resolutionTime,
bytes32 _pythPriceId
)
Creates a new prediction market
Validates that timestamps are correct (end time > current time, resolution time > end time)
Emits a MarketCreated event
Increments the market counter
fonction buyShares
function buyShares(uint256 marketId, bool isYes, uint256 _amount)
Permet aux utilisateurs d'acheter des parts dans un marché
_isYes : true pour « au-dessus du prix d'exercice », false pour « en dessous du prix d'exercice »
Prend une commission de 1 % du montant entré
Met à jour la position de l'utilisateur et le total des parts du marché
Transfère les jetons de paiement de l'utilisateur au contrat
Protégé contre la réentrance
fonction sellShares
function sellShares(uint256 marketId, bool isYes, uint256 _amount)
Permet aux utilisateurs de vendre leurs parts de position
Vérifie que l'utilisateur dispose d'assez de parts à vendre
Prend une commission de 1 % du montant de la vente
Met à jour les positions et transfère les jetons à l'utilisateur
fonction resolveMarket
function resolveMarket(uint256 marketId, bytes[] calldata pythUpdateData)
Appelée après l'heure de fin du marché pour déterminer le résultat
Utilise l'oracle Pyth pour obtenir le prix final
Convertit le prix en 18 décimales pour une comparaison cohérente
Définit le résultat du marché (true si le prix >= prix d'exercice)
Marque le marché comme résolu
Nécessite un paiement pour la mise à jour de l'oracle Pyth
function claimRewards
function claimRewards(uint256 _marketId)
Permet aux gagnants de réclamer leurs récompenses après la résolution du marché
Calcule la récompense en fonction de la part du côté gagnant
Transfère les récompenses à l'utilisateur
Réinitialise la position de l'utilisateur pour éviter les doubles réclamations
Déploiement de notre contrat
Déployer notre contrat est très simple avec notre kit de développement. Mettez à jour votre fonction run dans le fichier script/Deployer.s.sol.
function run() public returns (CryptoPredictionsMarket) {
vm.startBroadcast();
OkidoToken token = new OkidoToken(1000000);
CryptoPredictionsMarket market = new CryptoPredictionsMarket(address(token), 0x2880aB155794e7179c9eE2e38200202908C17B43);
vm.stopBroadcast();
return market;
}
Cette fonction déploie le jeton (jeton ERC20 que nous utilisons comme monnaie) et passe l'adresse, ainsi que l'adresse du déploiement de l'oracle Pyth sur Morph, en tant qu'arguments au constructeur de notre contrat de marché de prédiction.Adresse morph de Pyth :
0x2880aB155794e7179c9eE2e38200202908C17B43
Votre script de déploiement devrait être similaire à ce qui suit :
Ensuite, nous exécutons les commandes pour déployer notre contrat sur Morph.
source .env
forge script script/Deployer.s.sol -rpc-url $RPC_URL -broadcast -legacy -private-key $PRIVATE_KEY
Configuration du frontend
.env
La première chose à faire est de créer un walletconnect id et de l'ajouter à notre fichier .env (renommez .env.example en .env).
Ensuite, exécutez yarn install
pour installer les dépendances et yarn dev
pour lancer notre serveur de développement.
page.tsx
Voici notre page d'accueil. Remplacez le code par défaut par celui ci-dessous.
import Markets from "@/components/Markets";
export default function Home() {
return (
<main className="container mx-auto px-4 py-8">
<h1 className="text-4xl font-bold mb-8 text-center">
Crypto Prediction Markets
</h1>
<Markets />
</main>
);
}
C'est également le meilleur moment pour installer la dépendance de l'oracle Pyth.
yarn add @pythnetwork/pyth-evm-js
Markets.tsx
Ce composant affiche tous les marchés créés. Dans votre dossier components
, créez un fichier Markets.tsx
et collez-y le code de ce gist. Nous allons parcourir certaines fonctions sélectionnées.
Création de marché :
const handleCreateMarket = async (e: React.FormEvent) => {
e.preventDefault();
try {
await writeContractAsync({
address: marketAddress,
abi: marketAbi,
functionName: "createMarket",
args: [
newMarket.cryptoPair,
parseUnits(newMarket.strikePrice, 18),
BigInt(Math.floor(new Date(newMarket.endTime).getTime() / 1000)),
BigInt(Math.floor(new Date(newMarket.resolutionTime).getTime() / 1000)),
newMarket.pythPriceId as 0x${string},
],
});
// Reset form and refetch markets
setIsCreatingMarket(false);
setNewMarket({ /* ... */ });
refetchMarkets();
} catch (error) {
console.error("Error creating market:", error);
}
};
Dans cette fonction, nous utilisons le hook writeContractAsync
de wagmi
pour créer un nouveau marché en appelant la fonction createMarket
sur notre contrat et en passant la paire de crypto, le prix cible, la date de fin, la date de résolution, et l’ID du prix (obtenu via Pyth. Chaque paire de crypto a un ID de prix unique).
Obtenir des autorisations
const { data: tokenAllowance, refetch: refetchAllowance } = useReadContract({
address: tokenAddress,
abi: tokenAbi,
functionName: "allowance",
args: [address, marketAddress],
}) as any;
const handleApproveTokens = async () => {
if (!isConnected) {
open();
return;
}
try {
await writeContractAsync({
address: tokenAddress,
abi: tokenAbi,
functionName: "approve",
args: [marketAddress, parseUnits("1000000", 18)], // Approve a large amount
});
console.log("Token approval successful");
refetchAllowance();
} catch (error) {
console.error("Error approving tokens:", error);
}
};
Dans cette fonction, nous utilisons le hook useReadContract
de wagmi
pour appeler la fonction allowance
sur le contrat de tokens pour vérifier si le contrat de marché dispose d'une allocation suffisante. Ensuite, nous appelons la fonction approve
pour permettre au contrat de marché de dépenser des fonds depuis notre portefeuille.
Market.sol
Le prochain composant que nous allons aborder est le composant Market
. Dans votre dossier components
, créez un autre fichier nommé Market.sol
et collez-y le code de ce this gist.
Achat d’actions
const handleBuy = () => {
try {
const buyShareTx = writeContractAsync({
address: marketAddress,
abi: marketAbi,
functionName: "buyShares",
args: [market.id, isYes, amount],
});
} catch (err: any) {
console.log("Transaction Failed: " + err.message);
}
};
La fonction handleBuy
appelle la fonction buyShares
dans le contrat et prend en paramètres l'ID du marché, la position de l’utilisateur (oui/non), et le montant.
Résolution des marchés
const handleResolveMarket = async () => {
try {
const connection = new EvmPriceServiceConnection(
"https://hermes.pyth.network"
);
const priceFeedUpdateData = await connection.getPriceFeedsUpdateData([
market.pythPriceId.toString(),
]);
const resolveMarketTx = writeContractAsync({
address: marketAddress,
abi: marketAbi,
functionName: "resolveMarket",
args: [market.id, priceFeedUpdateData as any],
value: parseEther("0.001"),
});
} catch (err: any) {
console.log("Transaction Failed: " + err.message);
}
};
Cette fonction crée d'abord une connexion au service de prix de Pyth. hermes.pyth.network
est le point de connexion de Pyth pour obtenir les mises à jour des prix. Il fournit des données de prix en temps réel.
Après avoir établi la connexion, elle récupère les données de mise à jour du flux de prix. Ce sont les données de prix les plus récentes, incluant le dernier prix, l'intervalle de confiance, l'horodatage et la signature de l’éditeur.
Ensuite, elle appelle la fonction resolveMarket
dans le contrat en passant l’ID du marché et les données de mise à jour tout en payant pour les données. À noter également que nous passons un montant arbitraire (0,001 ETH) uniquement pour des raisons de démonstration. Idéalement, vous pouvez appeler la méthode getUpdateFee
pour obtenir le montant exact de chaque mise à jour de prix.
En résumé :
Réseau Pyth -> Service de Prix -> Smart Contract -> Résolution du Marché
Connexion au contrat
Une fois que nous avons terminé l’interface utilisateur (même si cela peut générer beaucoup d’erreurs dans votre navigateur, voici la magie !), il est temps de se connecter au smart contract. D'abord, nous allons dans le dossier constants
et créons deux fichiers : marketAbi.ts
et tokenAbi.ts
.
C'est ici que nous allons coller les ABIs de nos contrats. Pour obtenir l'ABI, allez dans contracts/foundry
et localisez le dossier out
. Dans ce dossier, recherchez le nom de nos contrats (market
et token
respectivement) et copiez le fichier JSON.
De retour dans notre dossier constants
, collez le fichier JSON dans les fichiers respectifs (marketAbi.ts
et tokenAbi.ts
). Assurez-vous également de coller uniquement le tableau ABI sans inclure les métadonnées. Par souci de simplicité et à titre d'exemple, voici mes fichiers marketAbi.ts
et tokenAbi.ts
.
Enfin, dans le fichier index
de notre dossier constants
, nous le modifions pour exporter nos ABIs ainsi que les adresses de nos contrats market
et token
.
Tester le tout
C'est le moment le plus amusant. Tout d'abord, lançons yarn dev
pour vérifier que tout est en ordre. Votre écran devrait ressembler au mien ci-dessous.
Ensuite, nous connectons notre portefeuille (parfois cela peut se faire automatiquement) et nous obtenons ce magnifique modal, grâce à web3modal
, qui est déjà préconfiguré avec le kit (cool, non ? Les modals, wagmi
, viem
, etc. sont tous déjà configurés avec le kit).
Après avoir connecté notre portefeuille, nous devrions obtenir cet écran par défaut.
Enfin, nous créons un marché. Ce marché sera axé sur la paire de crypto ETH/USD et notre prix cible sera de 2700 $. Notre durée sera très courte pour des raisons de démonstration.
Après avoir créé un marché, les utilisateurs pourront prendre des positions "oui" ou "non". Mais pour ce faire, ils doivent acquérir des actions (stake). Donc, d'abord, nous appelons la fonction approve
pour permettre au contrat Market de transférer notre stake depuis notre portefeuille. Notez qu'après avoir accordé l'autorisation, le bouton approve
disparaît.
Une fois les autorisations accordées, il est temps d’acheter des actions. Notez que nous prenons une commission de 1 % sur chaque position que l’utilisateur prend (voir la fonction buyShares
dans notre contrat).
Lorsque l'époque de notre marché arrive à expiration, un nouveau bouton apparaît. Le bouton nous permet de "résoudre le marché". Cela signifie qu'il appelle la fonction handleResolveMarket
, qui se connecte et interroge l’oracle Pyth pour obtenir le prix exact de notre paire de crypto à la date de résolution. Cette fonction détermine les gagnants et les perdants sur le marché.
Après avoir résolu le marché, réclamez vos récompenses. Surveillez le solde de vos tokens pour voir les récompenses arriver.
Conclusion
Dans ce guide, nous avons parcouru la création d’une application décentralisée de marché de prédiction complète. De la configuration de notre environnement en utilisant le kit de démarrage morph à la déploiement de notre smart contract, puis à l'interaction avec celui-ci depuis le frontend.
J’ai également laissé un petit défi (comme je le ferai désormais) appelé "corriger le bug". Il y a un bug intentionnel dans notre dApp. Après avoir réclamé vos récompenses, même si le solde se met à jour et que les fonds sont envoyés au gagnant, cela ne réinitialise pas la quantité d’actions "oui/non" à zéro. Deux façons de le corriger : soit depuis la fonction resolveMarket
dans le contrat, soit en modifiant l’interface utilisateur pour indiquer que le montant a été réclamé et que le marché est fermé.
Envoyez des liens vers vos solutions sur discord et taguez-moi (ernestnnamdi) et je vous répondrai certainement. À bientôt !
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!