WEB3DEV

Cover image for Crie uma exchange descentralizada (DEX) na Avalanche
Arnaldo Pereira Campos Junior
Arnaldo Pereira Campos Junior

Posted on

Crie uma exchange descentralizada (DEX) na Avalanche

Introdução

Uma exchange descentralizada é uma rede que permite que qualquer pessoa troque tokens de criptomoeda numa blockchain realizando uma transação entre dois tokens, como o par AVAX-LINK. As exchanges centralizadas (CEX), como a Binance, são plataformas de negociação online que usam uma carteira para unir compradores e vendedores. Elas funcionam de maneira semelhante às contas de corretagem on-line, e é por isso que são tão populares entre os investidores. As trocas descentralizadas (DEX), como PancakeSwap ou Uniswap, são protocolos financeiros independentes alimentados por contratos inteligentes que permitem que os comerciantes de criptomoedas convertam suas participações.

A principal diferença entre exchangescentralizadas e descentralizadas é que a primeira mantém o controle sobre seus fundos enquanto você interage na plataforma de negociação, enquanto a segunda permite que você mantenha o controle de seu dinheiro ao negociar.

Pré-requisitos

Você deve ter passado por este tutorial Criar uma rede de teste local na Avalanche e ter realizado uma troca de cadeia cruzada por meio do tutorial Transferir AVAX entre X-Chain e C-Chain para obter tokens de teste AVAX para seu endereço C-Chain.

Requisitos

Crie o diretório AvaSwap e instale as dependências

Avaswap é uma exchange descentralizada no protocolo Avalanche que permite negociações de criptomoedas ponto a ponto (P2P) que são executadas sem livros de pedidos ou qualquer intermediário centralizado.

Abra uma nova guia de terminal para que possamos criar um diretório e instalar algumas outras dependências. Primeiro, navegue até o diretório no qual você pretende criar seu diretório de trabalho:

cd /path/to/directory
Enter fullscreen mode Exit fullscreen mode

Crie e insira um novo diretório chamado AvaSwap:

mkdir AvaSwap
cd AvaSwap
Enter fullscreen mode Exit fullscreen mode

Em seguida, crie um projeto truffle padrão:

truffle init
Enter fullscreen mode Exit fullscreen mode

Atualizar truffle-config.js

truffle-config.js é o arquivo de configuração criado quando você executa truffle init. Adicione o seguinte truffle-config.js para configurar a conexão HDWalletProvider a DataHub Avalanche RPC.

require('dotenv').config();
const HDWalletProvider = require("@truffle/hdwallet-provider");

// Credenciais de conta a partir das quais nosso contrato será implantado
const mnemonic = process.env.MNEMONIC;

//  Chave API da sua conta Datahub para rede de teste Avalanche Fuji const APIKEY = process.env.APIKEY;

module.exports = {
  networks: {
    fuji: {
      provider: function() {
            return new HDWalletProvider({mnemonic, providerOrUrl: `https://avalanche--fuji--rpc.datahub.figment.io/apikey/${APIKEY}/ext/bc/C/rpc`, chainId: "0xa869"})
      },
      network_id: "*",
      gas: 3000000,
      gasPrice: 470000000000,
      skipDryRun: true
    }
  },
  solc: {
    optimizer: {
      enabled: true,
      runs: 200
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Observe que você pode alterar o protocol, ip e port se desejar direcionar chamadas de API para um nó AvalancheGo diferente. Além disso, observe que estamos definindo gasPrince e gas com os valores apropriados para a Avalanche C-Chain.

Adicionar DevToken.sol

No diretório de contratos, crie Devtoken.sol e adicione o seguinte bloco de código:

// SPDX-License-Identifier: MIT

pragma solidity >=0.5.0;

// Defina um contrato chamado DevToken de acordo com os padrões de token ERC20 
contract DevToken {
    string public name = "Dev Token";
    string public symbol = "DEV";
    uint256 public totalSupply = 1000000000000000000000000; // 1 million tokens
    uint8 public decimals = 18;

// Crie um evento que será emitido quando um token for transferido
    event Transfer(address indexed _from, address indexed _to, uint256 _value);

// Crie um evento que será emitido quando um token for aprovado 
    event Approval(
        address indexed _owner,
        address indexed _spender,
        uint256 _value
    );

    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

// Crie um construtor e defina `balance = totalSuppy` ou seja, 1 milhão de tokens
constructor() public {
        balanceOf[msg.sender] = totalSupply;
    }

// Crie uma função de transferência de acordo com os padrões de token ERC20
    function transfer(address _to, uint256 _value)
        public
        returns (bool success)
    {
        require(balanceOf[msg.sender] >= _value);
        balanceOf[msg.sender] -= _value;
        balanceOf[_to] += _value;
        emit Transfer(msg.sender, _to, _value);
        return true;
    }


// Crie uma função de aprovação de acordo com os padrões de token ERC20
    function approve(address _spender, uint256 _value)
        public
        returns (bool success)
    {
        allowance[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);
        return true;
    }

// Crie uma função transferFrom de acordo com os padrões de token ERC20
    function transferFrom(
        address _from,
        address _to,
        uint256 _value
    ) public returns (bool success) {
        require(_value <= balanceOf[_from]);
        require(_value <= allowance[_from][msg.sender]);
        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;
        allowance[_from][msg.sender] -= _value;
        emit Transfer(_from, _to, _value);
        return true;
    }
}
Enter fullscreen mode Exit fullscreen mode

Adicionar AvaSwap.sol

No diretório de contratos, adicione um novo arquivo chamado AvaSwap.sol e adicione o seguinte bloco de código:

// SPDX-License-Identifier: MIT

pragma solidity 0.6.7;

// importe os contratos necessários
import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
import './DevToken.sol';

// Defina um contrato chamado Avaswap
contract AvaSwap {
  string public name = "AvaSwap Network Exchange";
  DevToken public Token;
  uint public rate;
  AggregatorV3Interface internal priceFeed;

// Crie um evento que será emitido quando um token for comprado
  event TokenPurchase(
    address account,
    address token,
    uint amount,
    uint rate
  );

// Crie um evento que será emitido quando um token for vendido
  event TokenSold(
    address account,
    address token,
    uint amount,
    uint rate
  );

//Crie um construtor e passe `DevToken Token` como parâmetro
  constructor(DevToken _Token) public {
    Token = _Token;
    priceFeed = AggregatorV3Interface(0x5498BB86BC934c8D34FDA08E81D444153d0D06aD);
    rate = uint256(getLatestPrice());
  }

  // Retorna o preço mais recente do Chainlink PriceFeed Oracle
  function getLatestPrice() public view returns (int) {
    (
      uint80 roundID,
      int price,
      uint startedAt,
      uint timeStamp,
      uint80 answeredInRound
    ) = priceFeed.latestRoundData();
    // Se a rodada ainda não estiver completa, o timestamp é 0
    require(timeStamp > 0, "Round not complete");
    return 1e18/price;
  }

// Calcular e comprar token de acordo com o preço do token
  function buyTokens() public payable {
    // Calcular o número de tokens para comprar:
    // Avax Amount * taxa de resgate 
    uint tokenAmount = msg.value * rate;
    Token.transfer(msg.sender, tokenAmount);
    // Emitir um evento
    emit TokenPurchase(msg.sender, address(Token), tokenAmount, rate);
  }

// Calcular e vender token de acordo com o preço do token
  function sellToken(uint _amount) public {
    // O usuário não pode vender mais tokens do que tem
    require(Token.balanceOf(msg.sender) >= _amount);
    // Calcule o valor do avax para resgatar
    uint avaxAmount =  _amount / rate;
    // Exigir que o AvaSwap tenha avax suficiente
    require(address(this).balance >= avaxAmount);
    // Realizar venda
    Token.transferFrom(msg.sender, address(this), _amount);
    msg.sender.transfer(avaxAmount);
    // Emitir um evento
    emit TokenSold(msg.sender, address(Token), _amount, rate);
  }
}
Enter fullscreen mode Exit fullscreen mode

Adicionar uma nova migração

Crie um novo arquivo no diretório migrations chamado 2_deploy_contracts.js e adicione o seguinte bloco de código. Isso lida com a implantação do contrato inteligente AvaSwap na blockchain.

const AvaSwap = artifacts.require("AvaSwap");
const DevToken = artifacts.require("DevToken");
module.exports = function (deployer) {
  // Implantar DevToken
  await deployer.deploy(DevToken, '5777');
  const devToken = await DevToken.deployed();
  // Implantar AvaSwap com DevToken
  await deployer.deploy(AvaSwap, devToken.address);
  avaSwap =  await AvaSwap.deployed();
  // Cunhar 0.001 DevToken para AvaSwap
  await devToken.transfer(avaSwap.address, '1000000000000000000000000');
};
Enter fullscreen mode Exit fullscreen mode

Compilar contratos com Truffle

Sempre que fizer uma alteração no AvaSwap.sol, você deve compilar os contratos novamente.

truffle compile
Enter fullscreen mode Exit fullscreen mode

Você deverá ver:

Compiling your contracts...
===========================
> Compiling ./contracts/Migrations.sol
> Compiling ./contracts/AvaSwap.sol
> Artifacts written to /path/to/build/contracts
> Compiled successfully using:
   - solc: 0.5.16+commit.9c3226ce.Emscripten.clang
Enter fullscreen mode Exit fullscreen mode

Crie e desbloqueie uma conta na C-Chain

Ao implantar contratos inteligentes para a C-Chain, o Truffle usará como padrão a primeira conta disponível fornecida pelo seu cliente C-Chain como o endereço from usado durante as migrações.

O Truffle tem um console muito útil que podemos usar para interagir com a blockchain e nosso contrato. Abra o console:

truffle console --network development
Enter fullscreen mode Exit fullscreen mode

Em seguida, no console, crie a conta:

truffle(development)> let account = await web3.eth.personal.newAccount()
Enter fullscreen mode Exit fullscreen mode

Isso retorna:

undefined
Enter fullscreen mode Exit fullscreen mode

Imprima a conta:

truffle(development)> account
Enter fullscreen mode Exit fullscreen mode

Isso imprime a conta:

'0x090172CD36e9f4906Af17B2C36D662E69f162282'
Enter fullscreen mode Exit fullscreen mode

Desbloqueie sua conta:

truffle(development)> await web3.eth.personal.unlockAccount(account)
Enter fullscreen mode Exit fullscreen mode

Isso retorna:

true
Enter fullscreen mode Exit fullscreen mode

Executar migrações

Agora está tudo pronto para executar as migrações e implantar o contrato:

truffle(development)> migrate --network development
Enter fullscreen mode Exit fullscreen mode

Você deverá ver:

Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.

Migrations dry-run (simulation)
===============================
> Network name:    'development-fork'
> Network id:      1
> Block gas limit: 99804786 (0x5f2e672)

1_initial_migration.js
======================

   Deploying 'Migrations'
   ----------------------
   > block number:        4
   > block timestamp:     1607734632
   > account:             0x34Cb796d4D6A3e7F41c4465C65b9056Fe2D3B8fD
   > balance:             1000.91683679
   > gas used:            176943 (0x2b32f)
   > gas price:           225 gwei
   > value sent:          0 ETH
   > total cost:          0.08316321 ETH

   -------------------------------------
   > Total cost:          0.08316321 ETH

2_deploy_contracts.js
=====================

   Deploying 'AvaSwap'
   -------------------
   > block number:        6
   > block timestamp:     1607734633
   > account:             0x34Cb796d4D6A3e7F41c4465C65b9056Fe2D3B8fD
   > balance:             1000.8587791
   > gas used:            96189 (0x177bd)
   > gas price:           225 gwei
   > value sent:          0 ETH
   > total cost:          0.04520883 ETH

   -------------------------------------
   > Total cost:          0.04520883 ETH

Summary
=======
> Total deployments:   2
> Final cost:          0.13542204 ETH
Enter fullscreen mode Exit fullscreen mode

Se você não criou uma conta na C-Chain, verá este erro:

Error: Expected parameter 'from' not passed to function.
Enter fullscreen mode Exit fullscreen mode

Se você não depositou fundos na conta, verá este erro:

Error:  *** Deployment Failed ***

"Migrations" could not deploy due to insufficient funds
   * Account:  0x090172CD36e9f4906Af17B2C36D662E69f162282
   * Balance:  0 wei
   * Message:  sender doesn't have enough funds to send tx. The upfront cost is: 1410000000000000000 and the sender's account only has: 0
   * Try:
      + Using an adequately funded account
Enter fullscreen mode Exit fullscreen mode

Se você não desbloqueou a conta, verá este erro:

Error:  *** Deployment Failed ***

"Migrations" -- Returned error: authentication needed: password or unlock.
Enter fullscreen mode Exit fullscreen mode

Interação da interface do usuário com contratos inteligentes

Para interagir com o contrato, criaremos um aplicativo React usando create-react-app .

Crie um projeto de placa de vídeo:

npx create-react-app my-app
Enter fullscreen mode Exit fullscreen mode

Acesse o diretório do projeto:

cd my-app
Enter fullscreen mode Exit fullscreen mode

Inicie o aplicativo de amostra:

npm start
Enter fullscreen mode Exit fullscreen mode

Agora você pode visualizar o AvaSwap no navegador.

Local: http://localhost:3000/
Enter fullscreen mode Exit fullscreen mode

Usaremos os componentes Main.js, BuyForm.js & SellForm.js para criar a interface do usuário e a integração do contrato inteligente.

Main.js

Nesse arquivo, devemos importar os componentes BuyForm e SellForm, em seguida, renderizá-los.

// Importar componentes e bibliotecas necessários
import React, { Component } from "react";
import BuyForm from "./BuyForm";
import SellForm from "./SellForm";

// Crie uma classe chamada `Main` que herda `Component` importado de `react`
class Main extends Component {
  constructor(props) {
    super(props);
    this.state = {
      currentForm: "buy",
    };
  }

// Método para lidar com a mudança de token quando os tokens são trocados como Link, Dai e DevToken
  handleTokenChange = (token) => {
    this.props.handleTokenChange(token);
  };

// Exiba os componentes no método de renderização e passe os parâmetros necessários
  render() {
    let content;
    if (this.state.currentForm === "buy")
      content = (
        <BuyForm
          selectedToken={this.props.selectedToken}
          ethBalance={this.props.ethBalance}
          tokenBalance={this.props.tokenBalance}
          buyTokens={this.props.buyTokens}
          handleTokenChange={this.handleTokenChange}
        />
      );
    else
      content = (
        <SellForm
          selectedToken={this.props.selectedToken}
          ethBalance={this.props.ethBalance}
          tokenBalance={this.props.tokenBalance}
          sellTokens={this.props.sellTokens}
          handleTokenChange={this.handleTokenChange}
        />
      );
    return (
      <div id="content" className="mt-3">
        <div className="d-flex justify-content-between mb-3">
          <button
            className={
              this.state.currentForm === "buy"
                ? "btn btn-primary"
                : "btn btn-light"
            }
            onClick={(event) => {
              this.setState({ currentForm: "buy" });
            }}
          >
            Buy
          </button>
          <span>&lt; &nbsp; &gt;</span>
          <button
            className={
              this.state.currentForm === "sell"
                ? "btn btn-primary"
                : "btn btn-light"
            }
            onClick={(event) => {
              this.setState({ currentForm: "sell" });
            }}
          >
            Sell
          </button>
        </div>

        <div className="card mb-4">
          <div className="card-body">{content}</div>
        </div>
      </div>
    );
  }
}

export default Main;
Enter fullscreen mode Exit fullscreen mode

Comprar tokens:

No componente BuyForm, importe os logotipos e ABIs dos tokens AVAX, Dai e ChainLink.

BuyForm.js

// Importar componentes, ativos e bibliotecas necessários
import React, { Component } from "react";
import avaxLogo from "../avax-logo.png";
import tokenLogo from "../token-logo.png";
import daiLogo from "../dai-logo.png";
import chainLinkLogo from "../chainlink-link-logo.png";

// Crie uma classe chamada `BuyForm` que herda `Component` importado de `React`
class BuyForm extends Component {
  constructor(props) {
    super(props);
    this.state = {
      output: "0",
      rate: 100,
      selected: props.selectedToken.name,
    };
  }

// Método para lidar com a mudança de token quando os tokens são trocados como Link, Dai e DevToken
  handleChange = (event) => {
    this.setState({ selected: event.target.value });
    this.props.handleTokenChange(event.target.value);
  };

// Exiba os componentes do formulário no método render e passe os estados e props necessários
  render() {
    let { selected, rate } = this.state;
    return (
      <form
        className="mb-5"
        onSubmit={(event) => {
          event.preventDefault();
          let avaxAmount;
          avaxAmount = this.input.value.toString();
          avaxAmount = window.web3.utils.toWei(avaxAmount, "Ether");
          this.props.buyTokens(avaxAmount);
        }}
      >
        <div>
          <label className="float-left">
            <b>Input</b>
          </label>
          <span className="float-right text-muted">
            Balance: {window.web3.utils.fromWei(this.props.ethBalance, "Ether")}
          </span>
        </div>
        <div className="input-group mb-4">
          <input
            type="text"
            onChange={(event) => {
              const avaxAmount = this.input.value.toString();
              this.setState({
                output: avaxAmount * rate,
              });
            }}
            ref={(input) => {
              this.input = input;
            }}
            placeholder="0"
            className="form-control form-control-lg"
            required
          />
          <div className="input-group-append">
            <div className="input-group-text">
              &nbsp;&nbsp;&nbsp;
              <img src={avaxLogo} height="32" alt="" />
              &nbsp;&nbsp;&nbsp; AVAX &nbsp;&nbsp;&nbsp;
            </div>
          </div>
        </div>
        <div>
          <label className="float-left">
            <b>Output</b>
          </label>
          <span className="float-right text-muted">
            Balance:{" "}
            {window.web3.utils.fromWei(this.props.tokenBalance, "Ether")}
          </span>
        </div>
        <div className="input-group mb-2">
          <input
            value={this.state.output}
            type="text"
            placeholder="0"
            className="form-control form-control-lg"
            disabled
          />
          <div className="input-group-append">
            <div className="input-group-text">
              <img
                src={
                  selected === "LINK"
                    ? chainLinkLogo
                    : selected === "DAI"
                    ? daiLogo
                    : tokenLogo
                }
                height="32"
                alt=""
              />
              &nbsp;
              <select onChange={this.handleChange}>
                <option defaultValue={selected}>LINK</option>
                <option defaultValue={selected}>DEV</option>
                <option defaultValue={selected}>DAI</option>
              </select>
            </div>
          </div>
        </div>
        <div className="mb-5">
          <span className="float-left text-muted">
            <b>Exchange Rate</b>
          </span>
          <span className="float-right text-muted">
            1 AVAX = {rate} {selected}
          </span>
        </div>
        <button type="submit" className="btn btn-primary btn-block btn-lg">
          SWAP!
        </button>
      </form>
    );
  }
}

export default BuyForm;
Enter fullscreen mode Exit fullscreen mode

Vender tokens:

SellForm.js:

// Importar componentes, ativos e bibliotecas necessários
import React, { Component } from "react";
import avaxLogo from "../avax-logo.png";
import tokenLogo from "../token-logo.png";
import daiLogo from "../dai-logo.png";
import chainLinkLogo from "../chainlink-link-logo.png";

// Crie uma classe chamada `SellForm` que herda `Component` importado de `react`
class SellForm extends Component {
  constructor(props) {
    super(props);
    this.state = {
      output: "0",
      selected: props.selectedToken.name,
    };
  }

// Método para lidar com a mudança de token quando os tokens são trocados como Link, Dai e DevToken
  handleChange = (event) => {
    this.setState({ selected: event.target.value });
    this.props.handleTokenChange(event.target.value);
  };

// Exiba os componentes do formulário no método render e passe os estados e props necessários
  render() {
    let { selected } = this.state;
    return (
      <form
        className="mb-5"
        onSubmit={(event) => {
          event.preventDefault();
          let tokenAmount;
          tokenAmount = this.input.value.toString();
          tokenAmount = window.web3.utils.toWei(tokenAmount, "Ether");
          this.props.sellTokens(tokenAmount);
        }}
      >
        <div>
          <label className="float-left">
            <b>Input</b>
          </label>
          <span className="float-right text-muted">
            Balance:{" "}
            {window.web3.utils.fromWei(this.props.tokenBalance, "Ether")}
          </span>
        </div>
        <div className="input-group mb-4">
          <input
            type="text"
            onChange={(event) => {
              const tokenAmount = this.input.value.toString();
              this.setState({
                output: tokenAmount / 100,
              });
            }}
            ref={(input) => {
              this.input = input;
            }}
            placeholder="0"
            className="form-control form-control-lg"
            required
          />
          <div className="input-group-append">
            <div className="input-group-text">
              <img
                src={
                  selected === "LINK"
                    ? chainLinkLogo
                    : selected === "DAI"
                    ? daiLogo
                    : tokenLogo
                }
                height="32"
                alt=""
              />
              &nbsp;
              <select onChange={this.handleChange}>
                <option selected={selected === "LINK"} defaultValue="LINK">
                  LINK
                </option>
                <option selected={selected === "DEV"} defaultValue="DEV">
                  DEV
                </option>
                <option selected={selected === "DAI"} defaultValue="DAI">
                  DAI
                </option>
              </select>
            </div>
          </div>
        </div>
        <div>
          <label className="float-left">
            <b>Output</b>
          </label>
          <span className="float-right text-muted">
            Balance: {window.web3.utils.fromWei(this.props.ethBalance, "Ether")}
          </span>
        </div>
        <div className="input-group mb-2">
          <input
            value={this.state.output}
            type="text"
            placeholder="0"
            className="form-control form-control-lg"
            disabled
          />
          <div className="input-group-append">
            <div className="input-group-text">
              &nbsp;&nbsp;&nbsp;
              <img src={avaxLogo} height="32" alt="" />
              &nbsp;&nbsp;&nbsp; AVAX &nbsp;&nbsp;&nbsp;
            </div>
          </div>
        </div>
        <div className="mb-5">
          <span className="float-left text-muted">
            <b>Exchange Rate</b>
          </span>
          <span className="float-right text-muted">
            100 {selected} = 1 AVAX
          </span>
        </div>
        <button type="submit" className="btn btn-primary btn-block btn-lg">
          SWAP!
        </button>
      </form>
    );
  }
}

export default SellForm;
Enter fullscreen mode Exit fullscreen mode

Demonstração de exemplo:

alt_text

Conclusão

Agora você sabe como criar uma exchange descentralizada (DEX) com suíte Truffle e ReactJS na rede Avalanche.

Se você tiver alguma dificuldade em seguir este tutorial ou simplesmente quiser discutir a tecnologia Avalanche conosco, você pode se juntar ao nosso canal do discord !

Sobre o autor

Devendra Yadav

Referências

*https://learn.figment.io/tutorials/using-truffle-with-the-avalanche-c-chain
*https://github.com/OpenZeppelin/openzeppelin-contracts

Esse artigo foi escrito por Devendra Yadav e traduzido por Arnaldo Campos. Seu original pode ser lido Aqui.

Latest comments (0)