A simple exemple to create a smart contract who can exchange cryptos peer-to-peer (user to user) for a decentralized exchange.
On Etherscan click on "read contract" to available exchange, and how much they are.
Still on Etherscan, you can click on "write contract" to create an offer, cancel and order, or fill an order.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/// @title DecentralizedExchange
/// @notice Permet aux utilisateurs de créer et remplir des offres d’échange entre tokens ERC20.
contract DecentralizedExchange is ReentrancyGuard {
using SafeERC20 for IERC20;
uint256 public orderCounter;
struct Order {
uint256 id; // Identifiant unique de l'ordre
address maker; // Adresse du créateur de l'ordre
address tokenOffered; // Adresse du token proposé par le maker
uint256 amountOffered; // Quantité de tokens offerts
address tokenRequested; // Adresse du token demandé en échange
uint256 amountRequested; // Quantité de tokens demandés
uint256 expirationTime; // Timestamp d'expiration de l'ordre
bool active; // Indique si l'ordre est toujours actif
}
// Mapping des ordres par identifiant
mapping(uint256 => Order) public orders;
// Événements pour le suivi des actions
event OrderCreated(
uint256 indexed id,
address indexed maker,
address tokenOffered,
uint256 amountOffered,
address tokenRequested,
uint256 amountRequested,
uint256 expirationTime
);
event OrderCancelled(uint256 indexed id, address indexed maker);
event OrderFilled(uint256 indexed id, address indexed maker, address indexed filler);
/// @notice Crée une nouvelle offre d'échange
/// @param _tokenOffered L'adresse du token que le maker offre (ex : WETH)
/// @param _amountOffered La quantité de tokens offerts
/// @param _tokenRequested L'adresse du token que le maker souhaite obtenir (ex : USDT)
/// @param _amountRequested La quantité de tokens demandés en échange
/// @param _expirationTime Timestamp d'expiration de l'ordre (doit être supérieur au timestamp courant)
/// @return L'identifiant de l'ordre créé
///
/// Avant d'appeler cette fonction, le maker doit avoir approuvé le transfert de _amountOffered de _tokenOffered vers ce contrat.
function createOrder(
address _tokenOffered,
uint256 _amountOffered,
address _tokenRequested,
uint256 _amountRequested,
uint256 _expirationTime
) external returns (uint256) {
require(_tokenOffered != address(0) && _tokenRequested != address(0), "Adresse token invalide");
require(_amountOffered > 0 && _amountRequested > 0, "Les montants doivent etre superieurs a zero");
require(_expirationTime > block.timestamp, "Expiration invalide");
// Transfert des tokens offerts depuis le maker vers le contrat (mise en escrow)
IERC20(_tokenOffered).safeTransferFrom(msg.sender, address(this), _amountOffered);
orderCounter++;
orders[orderCounter] = Order({
id: orderCounter,
maker: msg.sender,
tokenOffered: _tokenOffered,
amountOffered: _amountOffered,
tokenRequested: _tokenRequested,
amountRequested: _amountRequested,
expirationTime: _expirationTime,
active: true
});
emit OrderCreated(orderCounter, msg.sender, _tokenOffered, _amountOffered, _tokenRequested, _amountRequested, _expirationTime);
return orderCounter;
}
/// @notice Annule un ordre actif. Seul le maker peut annuler son propre ordre.
/// @param _orderId L'identifiant de l'ordre à annuler
function cancelOrder(uint256 _orderId) external nonReentrant {
Order storage order = orders[_orderId];
require(order.active, "L'ordre n'est pas actif");
require(order.maker == msg.sender, "Seul le createur peut annuler");
order.active = false;
// Retour des tokens offerts au maker
IERC20(order.tokenOffered).safeTransfer(order.maker, order.amountOffered);
emit OrderCancelled(_orderId, msg.sender);
}
/// @notice Permet à un utilisateur de remplir un ordre actif.
/// @param _orderId L'identifiant de l'ordre à remplir
///
/// Le filler doit avoir approuvé le transfert de _amountRequested de _tokenRequested vers le maker.
function fillOrder(uint256 _orderId) external nonReentrant {
Order storage order = orders[_orderId];
require(order.active, "L'ordre n'est pas actif");
require(block.timestamp < order.expirationTime, "L'ordre a expire");
// Marquer l'ordre comme inactif avant les transferts pour éviter les attaques de réentrance
order.active = false;
// Transfert des tokens demandés depuis le filler vers le maker
IERC20(order.tokenRequested).safeTransferFrom(msg.sender, order.maker, order.amountRequested);
// Transfert des tokens offerts depuis le contrat vers le filler
IERC20(order.tokenOffered).safeTransfer(msg.sender, order.amountOffered);
emit OrderFilled(_orderId, order.maker, msg.sender);
}
/// @notice Permet à quiconque d'expirer un ordre et de retourner les tokens au maker si l'ordre est expiré.
/// @param _orderId L'identifiant de l'ordre à expirer
function expireOrder(uint256 _orderId) external nonReentrant {
Order storage order = orders[_orderId];
require(order.active, "L'ordre n'est pas actif");
require(block.timestamp >= order.expirationTime, "L'ordre n'est pas encore expire");
order.active = false;
// Retour des tokens offerts au maker
IERC20(order.tokenOffered).safeTransfer(order.maker, order.amountOffered);
emit OrderCancelled(_orderId, order.maker);
}
}