WEB3DEV

Cover image for Como Criar sua Própria Exchange Descentralizada (DEX) em Solidity e HardHat
Panegali
Panegali

Posted on

Como Criar sua Própria Exchange Descentralizada (DEX) em Solidity e HardHat

As exchanges descentralizadas (DEXs) desempenham um papel crucial no mundo das finanças descentralizadas (DeFi), oferecendo aos usuários controle total sobre seus ativos e, ao mesmo tempo, fornecendo uma plataforma para a transação de criptomoedas sem necessidade de confiança em terceiros. As exchanges centralizadas tradicionais (CEXs) exigem que os usuários depositem ativos na plataforma e liberem suas chaves privadas, criando um ponto de falha centralizado.

Você está interessado em criar sua própria exchange descentralizada na blockchain Ethereum? Não procure mais! Este artigo o guiará pelo processo, fornecendo exemplos de código em Solidity e configurando um ambiente de desenvolvimento usando o HardHat.

Funcionalidade da exchange descentralizada

Uma exchange descentralizada básica deve permitir aos usuários:

  1. Depositar tokens ou ether.
  2. Negociar tokens ou ether de maneira que não exija confiança em terceiros.
  3. Sacar tokens ou ether.

Para obter essa funcionalidade, construiremos nossa DEX usando a linguagem de programação de contrato inteligente mais popular da Ethereum, o Solidity. Para tornar nosso processo de desenvolvimento simplificado e eficiente, usaremos o ambiente de desenvolvimento do HardHat.

Configurando o ambiente de desenvolvimento

Primeiro, vamos configurar nosso ambiente de desenvolvimento usando o HardHat. Siga estas etapas simples:

  1. Instale o Node.js caso ainda não o tenha feito. Você pode baixá-lo aqui.
  2. Crie um novo diretório para seu projeto e navegue até ele em seu terminal ou prompt de comando.
  3. No diretório do projeto, execute o comando npm init para inicializar um novo projeto Node.js.
  4. Instale o HardHat executando npm install --save-dev hardhat.
  5. Execute npx hardhat para gerar um arquivo de configuração de amostra do HardHat e escolha criar um projeto de amostra quando o prompt for executado.
  6. Exclua os contratos de amostra (Greeter.sol e Token.sol), bem como o arquivo de teste (sample-test.js).

Agora, você tem um projeto HardHat em branco configurado e está pronto para começar a desenvolver sua própria exchange descentralizada do zero.

Criando um token para nossa exchange

Primeiro, criaremos um token ERC20 para nossa demonstração. Um token ERC20 é um padrão de token amplamente aceito na blockchain Ethereum, permitindo a interoperabilidade perfeita entre diferentes aplicativos descentralizados.

No diretório do seu projeto, crie um novo diretório chamado contracts. Dentro do diretório contracts, crie um novo arquivo chamado MyToken.sol. O código para nosso token ERC20 simples é o seguinte:

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

Aqui, usamos o contrato ERC20 fornecido pela biblioteca OpenZeppelin. Ela cuida dos detalhes da implementação do token ERC20, para que possamos simplesmente fornecer um suprimento inicial, o nome e o símbolo do nosso token.

Construindo o contrato da exchange descentralizada

Em seguida, crie um novo arquivo chamado DEX.sol no diretório contracts. É nele que desenvolveremos o contrato inteligente da nossa exchange descentralizada.

Inicialização

Vamos começar definindo a estrutura básica do nosso contrato e inicializando-o:

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 o SafeERC20 do OpenZeppelin para lidar com tokens de forma segura
    address public owner; // O proprietário da DEX

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

Aqui, importamos os contratos ERC20 e SafeERC20 da biblioteca OpenZeppelin. Também definimos o proprietário da DEX para o endereço que implanta o contrato.

Adicionando Suporte a Tokens

Agora, vamos construir a funcionalidade para adicionar novos tokens à nossa DEX:

struct Token {
    ERC20 tokenContract;
    uint256 totalLiquidity;
}

mapping(address => Token) public tokens; // Endereço do contrato de token para a estrutura do token

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

Definimos uma nova struct Token, que contém uma instância do contrato do token e a liquidez total do token em nossa DEX. Criamos um mapeamento do endereço do contrato do token para a struct Token. Em seguida, adicionamos uma função addToken que permite ao proprietário da DEX adicionar novos tokens fornecendo o endereço do contrato do token.

Observe a declaração require para garantir que apenas o proprietário possa adicionar tokens: ela verifica se o chamador da função é o proprietário e, caso não seja, a transação é revertida com uma mensagem de erro personalizada.

Funções de depósito e saque

A seguir, implementaremos as funções deposit (depósito) e withdraw (saque). Os usuários devem poder depositar e sacar tokens, bem como ether. Para depósitos e saques de tokens, usaremos as funções transfer e transferFrom fornecidas pelo contrato ERC20.

Crie as seguintes funções no arquivo DEX.sol:

mapping(address => mapping(address => uint256)) public tokenBalances; // Endereço do usuário -> endereço do token -> saldo do token

function depositToken(address tokenAddress, uint256 amount) public {
    require(tokenAddress != address(0), "Não é possível depositar token de endereço zero."); // Verificação de segurança
    require(tokens[tokenAddress].tokenContract != ERC20(address(0)), "Token não suportado pela DEX.");
    tokens[tokenAddress].tokenContract.safeTransferFrom(msg.sender, address(this), amount);
    tokenBalances[msg.sender][tokenAddress] += amount;
}

function deposit() payable public {
    require(msg.value > 0, "Não é possível depositar um valor zero."); // Verificação de segurança
    tokenBalances[msg.sender][address(0)] += msg.value; // O ETH é armazenado como endereço zero
}

mapping(address => mapping(address => uint256)) public etherBalances; // Endereço do usuário -> endereço do token -> saldo de ether

function withdrawToken(address tokenAddress, uint256 amount) public {
    require(tokenAddress != address(0), "Não é possível retirar token de endereço zero.");
    require(tokens[tokenAddress].tokenContract != ERC20(address(0)), "Token não suportado pela 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, "Balanço insuficiente de ether.");
    etherBalances[msg.sender][address(0)] -= amount;
    payable(msg.sender).transfer(amount);
}
Enter fullscreen mode Exit fullscreen mode

Com essas quatro funções, os usuários podem depositar e sacar tokens e ether e nós atualizamos seus saldos de acordo.

Negociação

Agora a parte mais importante: a funcionalidade de negociação!

Primeiro, crie uma struct para um pedido:

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

Em seguida, defina um array para armazenar pedidos em aberto e um mapeamento para armazenar o histórico de pedidos. Também implementamos um evento a ser emitido quando um novo pedido é criado:

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

Agora podemos criar a função para fazer um novo pedido:

function placeOrder(address token, uint256 tokensTotal, uint256 etherAmount) public {
    require(tokens[token].tokenContract != ERC20(address(0)), "Token não suportado pela DEX.");
    require(tokenBalances[msg.sender][token] >= tokensTotal, "Saldo insuficiente de tokens.");

    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

Por fim, vamos implementar a função fillOrder, permitindo que os usuários concluam pedidos em aberto:

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, "Saldo insuficiente de ether.");

    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

Com a função fillOrder, os usuários podem concluir pedidos, trocando seu ether por tokens. O pedido é removido da lista de pedidos em aberto e adicionado ao histórico de pedidos de cada parte.

Teste e implantação

Com nosso contrato DEX concluído, agora você pode implantá-lo em uma blockchain Ethereum local usando o HardHat, testar seu código ou até mesmo implantá-lo em uma rede pública de teste Ethereum.

Bom trabalho! Agora você criou sua própria exchange descentralizada na blockchain Ethereum. A partir daqui, você pode estender a funcionalidade da sua DEX, adicionar mais pares de negociação ou melhorar a interface do usuário implementando um aplicativo front-end.

Referências

  1. Ethereum.org: Solidity
  2. OpenZeppelin: Biblioteca de contratos inteligentes
  3. HardHat: Ambiente de Desenvolvimento Ethereum

O post Como criar sua própria exchange descentralizada (DEX) em Solidity e HardHat apareceu primeiro em CryptoLoom.


Artigo escrito por CryptoLoom. Traduzido por Marcelo Panegali.

Latest comments (0)