WEB3DEV Español

Cover image for Cómo crear tu Propio Intercambio Descentralizado (DEX) en Solidity y HardHat
Hector
Hector

Posted on • Updated on

Cómo crear tu Propio Intercambio Descentralizado (DEX) en Solidity y HardHat

Intercambios Descentralizados (Decentralized Exchanges, DEXes) juegan una parte crucial en el mundo de las finanzas descentralizadas (decentralized finance, DeFi), ofreciendo a sus usuarios control completo sobre sus activos y, simultáneamente, provee una plataforma para las transacciones de criptomonedas de confianza. Los lugares de intercambio centralizados tradicionales (traditional centralized exchanges, CEXes) requieren que los usuarios depositen sus activos en la plataforma y renuncien a sus claves privadas, creando un punto de falla centralizado.

¿Estás interesado en crear tu propio lugar de intercambio descentralizado en la blockchain de Ethereum? ¡No mires más! Este artículo te guiará a través del proceso, proveyendo ejemplos de código en Solidity y configurando un entorno de desarrollo usando HardHat.

¿Funcionalidad del Intercambio Descentralizado?

Un intercambio descentralizado básico debería permitir a los usuarios a:

  1. Depositar tokens o ether.
  2. Intercambiar tokens o ether en una forma de confianza.
  3. Retirar tokens o ether.

Configurando un entorno de Desarrollo

Primero, configuremos nuestro entorno usando HardHat. Sigue estos simples pasos:

  1. Instala Node.js si no lo has hecho aún. Puedes descargarlo aquí
  2. Crea un nuevo directorio para tu proyecto y navega en el en tu terminal o el prompt de tu comando
  3. En el directorio del proyecto, ejecuta el comando npm init para iniciar un nuevo proyecto Node.js
  4. Instala HardHat ejecutando npm install --save-dev hardhat
  5. Ejecuta npx hardhat para generar una muestra de archivo de configuración HardHat y elige crear una prueba de muestra cuando se solicite
  6. Borra los contratos de muestra (Greeter.sol y Token.sol) así como el archivo de prueba (sample-test.js)

Ahora, tienes un proyecto configurado en blanco de HardHat y estás listo para empezar a escribir tu propio intercambio descentralizado desde cero.

Crear un Token para Nuestro Intercambio

Primero, crearemos un token ERC20 para nuestra demostración. Un token ERC20 es un token estándar totalmente aceptable en la blockchain de Ethereum, permitiendo una interoperabilidad sin problemas entre diferentes aplicaciones descentralizadas.

En el directorio de tu proyecto, crea un nuevo directorio llamado contracts. Dentro del directorio contracts, crea un nuevo archivo llamado ´MyToken.sol´. El código de nuestro código sencillo ERC20 es como sigue:

pragma solidity ^0.8.0;

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

contract MyToken is ERC20 {
    constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
        _mint(msg.sender, initialSupply);
    }
}
Enter fullscreen mode Exit fullscreen mode

Aquí, podemos hacer uso del contrato ERC20 proveído por la biblioteca OpenZeppelin. Se encarga de los detalles de la implementación del token ERC20, así que podemos simplemente proveer un suministro inicial y el nombre y símbolo de nuestro token.

Construir un Contrato de Intercambio Descentralizado

Ahora, crea un nuevo archivo llamado DEX.sol en el directorio contracts. Aquí es donde desarrollaremos nuestro contrato inteligente para el intercambio descentralizado.

Inicialización

Comencemos definiendo la estructura básica de nuestro contrato y inicializarlo:

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract DEX {
    using SafeERC20 for ERC20; // Use OpenZeppelin's SafeERC20 to deal with tokens securely
    address public owner; // The owner of the DEX

    constructor() {
        owner = msg.sender;
    }
}
Enter fullscreen mode Exit fullscreen mode

Aquí, importaremos ambos: los contratos ERC20 y SafeERC20 desde la biblioteca OpenZeppelin. También configuraremos el dueño del DEX a la dirección que despliega el contrato.

Añadiendo soporte al Token

Ahora, construiremos la funcionalidad para añadir nuevos tokens a nuestro DEX:

struct Token {
    ERC20 tokenContract;
    uint256 totalLiquidity;
}

mapping(address => Token) public tokens; // Address of token contract to Token struct

function addToken(address tokenAddress) public {
    require(msg.sender == owner, "Only owner can add tokens.");
    ERC20 token = ERC20(tokenAddress);
    tokens[tokenAddress] = Token({tokenContract: token, totalLiquidity: 0});
}
Enter fullscreen mode Exit fullscreen mode

Podemos definir una nueva estructura Token, el cual mantiene una instancia del contrato del token y la liquidez total del token en nuestro DEX. Crearemos un mapping desde la dirección del contrato del token a la estructura del Token. Luego, añadiremos la función addToken que permite al dueño del DEX añadir algunos nuevos tokens proveyendo la dirección del contrato del token.

Date cuenta que la declaración require para asegurar que sólo el dueño pueda añadir tokens: revisa si la función de llamada es el dueño y, si no lo es, la transacción es revertida con un mensaje de error personalizado.

Funciones de Depósito y Retiro

Luego, implementaremos las funciones de deposit y withdraw. Los usuarios deberían ser capaces de depositar y retirar tokens así como ether. Para los depósitos y retiradas de token, usaremos las funciones de transfer y transferFrom, proveídas por el contrato ERC20.
Crea las siguientes funciones en el archivo DEX.sol:

mapping(address => mapping(address => uint256)) public tokenBalances; // User address -> token address -> token balance

function depositToken(address tokenAddress, uint256 amount) public {
    require(tokenAddress != address(0), "Cannot deposit zero address token."); // Safety check
    require(tokens[tokenAddress].tokenContract != ERC20(address(0)), "Token not supported by DEX.");
    tokens[tokenAddress].tokenContract.safeTransferFrom(msg.sender, address(this), amount);
    tokenBalances[msg.sender][tokenAddress] += amount;
}

function deposit() payable public {
    require(msg.value > 0, "Cannot deposit zero value."); // Safety check
    tokenBalances[msg.sender][address(0)] += msg.value; // ETH is stored as zero address
}

mapping(address => mapping(address => uint256)) public etherBalances; // User address -> token address -> ether balance

function withdrawToken(address tokenAddress, uint256 amount) public {
    require(tokenAddress != address(0), "Cannot withdraw zero address token.");
    require(tokens[tokenAddress].tokenContract != ERC20(address(0)), "Token not supported by DEX.");
    tokenBalances[msg.sender][tokenAddress] -= amount;
    tokens[tokenAddress].tokenContract.safeTransfer(msg.sender, amount);
}

function withdraw(uint256 amount) public {
    require(etherBalances[msg.sender][address(0)] >= amount, "Insufficient ether balance.");
    etherBalances[msg.sender][address(0)] -= amount;
    payable(msg.sender).transfer(amount);
}
Enter fullscreen mode Exit fullscreen mode

Con estas cuatro funciones, los usuarios pueden depositar y retirar ambos tokens y ether, y actualizaremos sus balances apropiadamente.

Intercambio

Ahora, la parte más importante: ¡la funcionalidad de intercambio (trading)!

Primero, crearemos una estructura para un orden:

struct Order {
    address trader;
    address token;
    uint256 tokensTotal;
    uint256 tokensLeft;
    uint256 etherAmount;
    uint256 filled;
}
Enter fullscreen mode Exit fullscreen mode

Luego, definiremos un array para almacenar órdenes abiertas y un mapping para almacenar el historial de almacenamiento. También implementaremos un evento para emitirla cuando una nueva orden se crea:

Order[] public openOrders;
mapping(address => Order[]) public orderHistories;

event OrderPlaced(uint256 orderId, address indexed trader, address indexed token, uint256 tokensTotal, uint256 etherAmount);
Enter fullscreen mode Exit fullscreen mode

Ahora podemos crear la función para colocar una nueva orden:

function placeOrder(address token, uint256 tokensTotal, uint256 etherAmount) public {
    require(tokens[token].tokenContract != ERC20(address(0)), "Token not supported by DEX.");
    require(tokenBalances[msg.sender][token] >= tokensTotal, "Insufficient token balance.");

    Order memory newOrder = Order({
        trader: msg.sender,
        token: token,
        tokensTotal: tokensTotal,
        tokensLeft: tokensTotal,
        etherAmount: etherAmount,
        filled: 0
    });

    openOrders.push(newOrder);
    tokenBalances[msg.sender][token] -= tokensTotal;

    emit OrderPlaced(openOrders.length-1, msg.sender, token, tokensTotal, etherAmount);
}
Enter fullscreen mode Exit fullscreen mode

Finalmente implementaremos la función fillOrder, permitiendo a los usuarios completar órdenes abiertas:

event OrderFilled(uint256 orderId, address indexed trader, uint256 tokensFilled, uint256 etherTransferred);

function fillOrder(uint256 orderId) public {
    Order storage order = openOrders[orderId];
    uint256 tokensToFill = order.tokensLeft;
    uint256 etherToFill = (tokensToFill * order.etherAmount) / order.tokensTotal;

    require(etherBalances[msg.sender][address(0)] >= etherToFill, "Insufficient ether balance.");

    etherBalances[order.trader][address(0)] += etherToFill;
    etherBalances[msg.sender][address(0)] -= etherToFill;
    tokenBalances[msg.sender][order.token] += tokensToFill;
    order.tokensLeft = 0;
    order.filled += tokensToFill;

    orderHistories[msg.sender].push(order);
    if (order.trader != msg.sender) orderHistories[order.trader].push(order);
    delete openOrders[orderId];

    emit OrderFilled(orderId, order.trader, tokensToFill, etherToFill);
}
Enter fullscreen mode Exit fullscreen mode

Con la función fill Order, los usuarios pueden completar las órdenes, intercambiando sus ether por tokens. Luego, la orden es removida desde el listado de órdenes abiertas y luego añadidas al historial de orden de cada grupo.

Prueba y Despliegue

Con nuestro contrato DEX completo, puedes desplegarlo en una blockchain Ethereum local en HardHat, prueba tu código o incluso, despliega en una red de prueba Ethereum.

¡Bien hecho! Ahora haz creado tu propio intercambio descentralizado en la blockchain de Ethereum. Desde aquí, puedes extender la función de tu DEX, añade más intercambios de par o mejora la interfaz del usuario, implementando una aplicación frontend.

Referencias

  1. Ethereum.org: Solidity
  2. OpenZeppelin: Biblioteca del Contrato Inteligente
  3. HardHat:Entorno de Desarrollo Ethereum

El post Cómo Crear Tu Propio Intercambio Descentralizado (DEX) en Solidity y HardHat apareció primero en CriptoLoom.

Este artículo es una traducción de CryptoLoom, hecha por Héctor Botero. Puedes encontrar el artículo original aquí.
Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.
Si prefieres puedes escribirnos a @web3dev_es en Twitter.

Discussion (0)