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
- NodeJS
- ReactJS
- Truffle, que você pode instalar com
npm install -g truffle
- Instale a extensão Metamask em seu navegador.
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
Crie e insira um novo diretório chamado AvaSwap
:
mkdir AvaSwap
cd AvaSwap
Em seguida, crie um projeto truffle padrão:
truffle init
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
}
}
}
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;
}
}
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);
}
}
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');
};
Compilar contratos com Truffle
Sempre que fizer uma alteração no AvaSwap.sol
, você deve compilar os contratos novamente.
truffle compile
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
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
Em seguida, no console, crie a conta:
truffle(development)> let account = await web3.eth.personal.newAccount()
Isso retorna:
undefined
Imprima a conta:
truffle(development)> account
Isso imprime a conta:
'0x090172CD36e9f4906Af17B2C36D662E69f162282'
Desbloqueie sua conta:
truffle(development)> await web3.eth.personal.unlockAccount(account)
Isso retorna:
true
Executar migrações
Agora está tudo pronto para executar as migrações e implantar o contrato:
truffle(development)> migrate --network development
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
Se você não criou uma conta na C-Chain, verá este erro:
Error: Expected parameter 'from' not passed to function.
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
Se você não desbloqueou a conta, verá este erro:
Error: *** Deployment Failed ***
"Migrations" -- Returned error: authentication needed: password or unlock.
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
Acesse o diretório do projeto:
cd my-app
Inicie o aplicativo de amostra:
npm start
Agora você pode visualizar o AvaSwap no navegador.
Local: http://localhost:3000/
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>< ></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;
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">
<img src={avaxLogo} height="32" alt="" />
AVAX
</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=""
/>
<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;
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=""
/>
<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">
<img src={avaxLogo} height="32" alt="" />
AVAX
</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;
Demonstração de exemplo:
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
Referências
*https://learn.figment.io/tutorials/using-truffle-with-the-avalanche-c-chain
*https://github.com/OpenZeppelin/openzeppelin-contracts
- https://github.com/makerdao/dss *https://github.com/smartcontractkit/LinkToken
- https://github.com/devilla/Avaswap *https://trustwallet.com/blog/trading-on-cex-vs-dex
Esse artigo foi escrito por Devendra Yadav e traduzido por Arnaldo Campos. Seu original pode ser lido Aqui.
Latest comments (0)