WEB3DEV

Cover image for Como usar o Uniswap para trocar tokens a partir de um frontend personalizado
Adriano P. Araujo
Adriano P. Araujo

Posted on

Como usar o Uniswap para trocar tokens a partir de um frontend personalizado

Quão legal seria criar seu próprio dapp, que permitisse trocar todos os tipos de tokens sem precisar escrever uma única linha de código de Solidity? Bem, com o Uniswap SDK, você pode usar os contratos inteligentes do Uniswap para trocar um monte de tokens ERC20 com seu próprio front-end personalizado.

Neste tutorial, vamos criar um dApp React que permite trocar ETH por UNI. 

O tutorial concentra-se na lógica real e em como se conectar com a interface do usuário com o agente de usuário padrão CSS. Não contém estilos personalizados. No entanto, também criei uma versão estilizada do dApp. 

Se você estiver interessado em como isso foi feito, vá até a parte final da página em que vinculei o repositório e o site real.

Troque ETH por UNI usando Uniswap em segundo plano

Para seguir o conhecimento básico do tutorial sobre npm, é necessário React, ethers.js e wagmi.sh.

Primeiro, precisamos criar nosso dApp React. Usaremos o TypeScript para este projeto. Então abra seu terminal e escreva:


npx create-react-app uniswap-dapp --template typescript

Enter fullscreen mode Exit fullscreen mode

Em seguida, altere o diretório para o recém-criado e instale as dependências:


cd uniswap-dapp

npm install ethers wagmi @ uniswap / v3-sdk @ uniswap / sdk-core

Enter fullscreen mode Exit fullscreen mode

Agora inicie o servidor de desenvolvimento.


npm init

Enter fullscreen mode Exit fullscreen mode

Seu navegador da web padrão deve abrir e exibir a infame tela do React.

Em seguida, abra o projeto recém-criado no seu editor de código favorito. Para começar com uma configuração limpa, exclua todos os códigos do boilerplate. Exclua tudo dos arquivos index.css e App.css. Exclua o arquivo 'logo.svg' e remova a maioria das coisas dentro do arquivo App.tsx para que fique assim:


import React from 'react';

import './App.css';



function App() {

return <div></div>;

}



export default App;

Enter fullscreen mode Exit fullscreen mode

Agora podemos começar!

Conexão da carteira

Vamos usar a biblioteca wagmi.sh para uma conexão fácil com a carteira MetaMask. Se você já sabe como configurar uma conexão de carteira com o MetaMask e o wagmi.sh, fique à vontade para pular esta seção.

Para conectar nosso aplicativo a um nó Ethereum e poder acessar informações relacionadas ao blockchain de qualquer lugar do nosso aplicativo, precisamos encerrar todo o aplicativo com o provedor WagmiConfig e passar suas opções de configuração. Siga para App.tsx e altere o arquivo da seguinte maneira:


import React from 'react';

import { chain, configureChains, WagmiConfig, createClient } from 'wagmi';

import { publicProvider } from 'wagmi/providers/public';

import './App.css';

const { provider, webSocketProvider } = configureChains(

  [chain.goerli],

  [publicProvider()]

);

const client = createClient({ autoConnect: true, provider, webSocketProvider });

function App() {

  return <WagmiConfig client={client}></WagmiConfig>;

}

export default App;

Enter fullscreen mode Exit fullscreen mode

Para esta demonstração, usaremos a testnet Goerli. Também usamos um provedor público, pois não precisamos enviar muitas transações. Se o seu dApp exceder os limites de taxa do provedor público, você precisará usar serviços como Alchemy ou Infura. Com isso, estamos conectados com sucesso a um nó Ethereum e já podemos ler dados da blockchain. No entanto, para fazer transações e alterar o estado blockchain, precisamos conectar nosso dApp a uma carteira.

Implementaremos nosso botão de conexão da carteira e lógica dentro do cabeçalho do dApp. Crie um novo componente Header dentro de um novo diretório chamado components com o seguinte conteúdo:

O InjectedConnector refere-se à extensão MetaMask e garante que a MetaMask use Goerli como sua cadeia, passando a propriedade chainId com seu valor definido como o ID da Goerli. Agora devemos poder conectar nossa carteira MetaMask ao dApp com este belo botão que aparece na página:

Após uma conexão bem-sucedida, o botão se transforma no endereço da carteira atualmente conectada.

Buscando preços à vista

Agora que a conexão da carteira foi resolvida, podemos nos concentrar no tópico real deste artigo: Como usar o Uniswap para trocar tokens de um front-end personalizado. Começaremos criando um novo custom hook chamado useSwap em um novo diretório chamado hooks.


const useSwap = () => {};

export default useSwap;

Enter fullscreen mode Exit fullscreen mode

Para buscar os preços dos tokens, precisamos interagir com o contrato de um pool que inclua esse par de tokens. Como dito no começo, vamos interagir com o pool ETH-UNI. Internamente, o ETH é tratado como Ether ou WETH, porque o Uniswap pode lidar apenas com tokens ERC20 e o próprio ETH não é um token, mas uma moeda. Portanto, o pool que estamos procurando é na verdade um pool WETH-UNI. Vou explicar isso com mais detalhes mais adiante. Por enquanto, obteremos uma referência ao contrato do pool assim:


import { useContract, useProvider } from 'wagmi';

import IUniswapV3PoolArtifact from '@uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json';

// WETH - UNI pool com taxa de 0.3% 

const POOL_ADDRESS = '0x07A4f63f643fE39261140DF5E613b9469eccEC86';

const useSwap = () => {

  const provider = useProvider();

  const poolContract = useContract({

    address: POOL_ADDRESS,

    abi: IUniswapV3PoolArtifact.abi,

    signerOrProvider: provider,

  });

};

export default useSwap;

Enter fullscreen mode Exit fullscreen mode

Como leremos apenas esse contrato e não faremos transações, basta usar um provedor em vez de um assinante ( um provedor com uma carteira conectada a ele ).

Em seguida, criamos duas interfaces Immutables e State modelando os dados do pool na cadeia de que precisamos.


interface Immutables {

  token0: string;

  token1: string;

  fee: number;

}

interface State {

  liquidity: ethers.BigNumber;

  sqrtPriceX96: ethers.BigNumber;

  tick: number;

}

Enter fullscreen mode Exit fullscreen mode

Criamos duas funções getPoolImmutables e getPoolState, que buscam  e retornam os dados correspondentes à sua respectiva interface.


const getPoolImmutables = async () => {

  if (!poolContract)

    throw new Error('Pool contract has not been initialized');

  const [token0, token1, fee] = await Promise.all([

    poolContract.token0(),

    poolContract.token1(),

    poolContract.fee(),

  ]);

  const immutables: Immutables = {

    token0,

    token1,

    fee,

  };

  return immutables;

};

const getPoolState = async () => {

  if (!poolContract)

    throw new Error('Pool contract has not been initialized');

  const [liquidity, slot] = await Promise.all([

    poolContract.liquidity(),

    poolContract.slot0(),

  ]);

  const PoolState: State = {

    liquidity,

    sqrtPriceX96: slot[0],

    tick: slot[1],

  };

  return PoolState;

};

Enter fullscreen mode Exit fullscreen mode

Com isso, já temos todos os dados de blockchain necessários para fazer uma cotação.

Adicionamos mais duas constantes à parte superior do arquivo.


const WETH_DECIMALS = 18;

const UNI_DECIMALS = 18;

Enter fullscreen mode Exit fullscreen mode

E agora podemos implementar a função getQuote que retorna a quantidade de UNI que obtemos por uma quantidade específica de ETH.


const getQuote = async (amount: number) => {

  const [immutables, state] = await Promise.all([

    getPoolImmutables(),

    getPoolState(),

  ]);

  const tokenA = new Token(chainId.goerli, immutables.token0, UNI_DECIMALS);

  const tokenB = new Token(chainId.goerli, immutables.token1, WETH_DECIMALS);

  const pool = new Pool(

    tokenA,

    tokenB,

    immutables.fee,

    state.sqrtPriceX96.toString(),

    state.liquidity.toString(),

    state.tick

  );

  const outputAmount = amount * parseFloat(pool.token1Price.toFixed(2));

  return outputAmount;

};

Enter fullscreen mode Exit fullscreen mode

A função chama as duas outras funções que definimos anteriormente para obter os dados atuais da blockchain. O que é interessante notar aqui é que o token0 do pool é UNI e o token1 do pool é WETH. Depois disso as classes Token e Pool da Uniswap são instanciadas com os dados retornados. Finalmente, obtemos a cotação multiplicando a quantidade de ETH pelo preço de UNI em relação ao ETH.

Juntando todas as peças e retornando a função getQuote do componente seu arquivo agora deve ter a seguinte aparência:


import { chainId, useContract, useProvider } from 'wagmi';

import IUniswapV3PoolArtifact from '@uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json';

import { ethers } from 'ethers';

import { Token } from '@uniswap/sdk-core';

import { Pool } from '@uniswap/v3-sdk';

interface Immutables {

  token0: string;

  token1: string;

  fee: number;

}

interface State {

  liquidity: ethers.BigNumber;

  sqrtPriceX96: ethers.BigNumber;

  tick: number;

}

const WETH_DECIMALS = 18;

const UNI_DECIMALS = 18;

// WETH - UNI pool with 0.3% fee

const POOL_ADDRESS = '0x07A4f63f643fE39261140DF5E613b9469eccEC86';

const useSwap = () => {

  const provider = useProvider();

  const poolContract = useContract({

    address: POOL_ADDRESS,

    abi: IUniswapV3PoolArtifact.abi,

    signerOrProvider: provider,

  });

  const getQuote = async (amount: number) => {

    const [immutables, state] = await Promise.all([

      getPoolImmutables(),

      getPoolState(),

    ]);

    const tokenA = new Token(chainId.goerli, immutables.token0, UNI_DECIMALS);

    const tokenB = new Token(chainId.goerli, immutables.token1, WETH_DECIMALS);

    const pool = new Pool(

      tokenA,

      tokenB,

      immutables.fee,

      state.sqrtPriceX96.toString(),

      state.liquidity.toString(),

      state.tick

    );

    const outputAmount = amount * parseFloat(pool.token1Price.toFixed(2));

    return outputAmount;

  };

  const getPoolImmutables = async () => {

    if (!poolContract)

      throw new Error('Pool contract has not been initialized');

    const [token0, token1, fee] = await Promise.all([

      poolContract.token0(),

      poolContract.token1(),

      poolContract.fee(),

    ]);

    const immutables: Immutables = {

      token0,

      token1,

      fee,

    };

    return immutables;

  };

  const getPoolState = async () => {

    if (!poolContract)

      throw new Error('Pool contract has not been initialized');

    const [liquidity, slot] = await Promise.all([

      poolContract.liquidity(),

      poolContract.slot0(),

    ]);

    const PoolState: State = {

      liquidity,

      sqrtPriceX96: slot[0],

      tick: slot[1],

    };

    return PoolState;

  };

  return { getQuote };

};

export default useSwap;

Enter fullscreen mode Exit fullscreen mode

Agora podemos começar a criar a interface do usuário que chama a função e atualiza de acordo com a cotação retornada. Crie um novo componente chamado SwapCard com o seguinte conteúdo:

Agora, se você digitar qualquer número no campo de entrada para ETH, a quantidade esperada de UNI que você obteria se executasse o swap será exibida no segundo campo de entrada. Seu site agora deve ter a seguinte aparência:

Transforme ETH em WETH e aprove os gastos

Agora, antes que possamos pedir ao roteador Uniswap v3 que troque nosso ETH para UNI, precisamos converter nosso ETH em WETH e permitir que o contrato do roteador use WETH em nosso nome. Como mencionei anteriormente, na verdade não há pool que inclua ETH diretamente. O Uniswap funciona apenas com tokens ERC20, e é por isso que precisamos converter nosso ETH para o token ERC20, que representa ETH que é WETH.

Então começamos criando um novo arquivo chamado useWETH.tsx que é um custom hook do React, assim como o useSwap.


const useWETH = () => {};

export default useWETH;

Enter fullscreen mode Exit fullscreen mode

Em seguida, queremos criar a instância de contrato do token WETH. Para isso, precisamos do ABI token. Crie um novo diretório chamado abis e um novo arquivo chamado WETH.json no diretório com este conteúdo:


{

  "abi": [

    {

      "constant": true,

      "inputs": [],

      "name": "name",

      "outputs": [{ "name": "", "type": "string" }],

      "payable": false,

      "stateMutability": "view",

      "type": "function"

    },

    {

      "constant": false,

      "inputs": [

        { "name": "guy", "type": "address" },

        { "name": "wad", "type": "uint256" }

      ],

      "name": "approve",

      "outputs": [{ "name": "", "type": "bool" }],

      "payable": false,

      "stateMutability": "nonpayable",

      "type": "function"

    },

    {

      "constant": true,

      "inputs": [],

      "name": "totalSupply",

      "outputs": [{ "name": "", "type": "uint256" }],

      "payable": false,

      "stateMutability": "view",

      "type": "function"

    },

    {

      "constant": false,

      "inputs": [

        { "name": "src", "type": "address" },

        { "name": "dst", "type": "address" },

        { "name": "wad", "type": "uint256" }

      ],

      "name": "transferFrom",

      "outputs": [{ "name": "", "type": "bool" }],

      "payable": false,

      "stateMutability": "nonpayable",

      "type": "function"

    },

    {

      "constant": false,

      "inputs": [{ "name": "wad", "type": "uint256" }],

      "name": "withdraw",

      "outputs": [],

      "payable": false,

      "stateMutability": "nonpayable",

      "type": "function"

    },

    {

      "constant": true,

      "inputs": [],

      "name": "decimals",

      "outputs": [{ "name": "", "type": "uint8" }],

      "payable": false,

      "stateMutability": "view",

      "type": "function"

    },

    {

      "constant": true,

      "inputs": [{ "name": "", "type": "address" }],

      "name": "balanceOf",

      "outputs": [{ "name": "", "type": "uint256" }],

      "payable": false,

      "stateMutability": "view",

      "type": "function"

    },

    {

      "constant": true,

      "inputs": [],

      "name": "symbol",

      "outputs": [{ "name": "", "type": "string" }],

      "payable": false,

      "stateMutability": "view",

      "type": "function"

    },

    {

      "constant": false,

      "inputs": [

        { "name": "dst", "type": "address" },

        { "name": "wad", "type": "uint256" }

      ],

      "name": "transfer",

      "outputs": [{ "name": "", "type": "bool" }],

      "payable": false,

      "stateMutability": "nonpayable",

      "type": "function"

    },

    {

      "constant": false,

      "inputs": [],

      "name": "deposit",

      "outputs": [],

      "payable": true,

      "stateMutability": "payable",

      "type": "function"

    },

    {

      "constant": true,

      "inputs": [

        { "name": "", "type": "address" },

        { "name": "", "type": "address" }

      ],

      "name": "allowance",

      "outputs": [{ "name": "", "type": "uint256" }],

      "payable": false,

      "stateMutability": "view",

      "type": "function"

    },

    { "payable": true, "stateMutability": "payable", "type": "fallback" },

    {

      "anonymous": false,

      "inputs": [

        { "indexed": true, "name": "src", "type": "address" },

        { "indexed": true, "name": "guy", "type": "address" },

        { "indexed": false, "name": "wad", "type": "uint256" }

      ],

      "name": "Approval",

      "type": "event"

    },

    {

      "anonymous": false,

      "inputs": [

        { "indexed": true, "name": "src", "type": "address" },

        { "indexed": true, "name": "dst", "type": "address" },

        { "indexed": false, "name": "wad", "type": "uint256" }

      ],

      "name": "Transfer",

      "type": "event"

    },

    {

      "anonymous": false,

      "inputs": [

        { "indexed": true, "name": "dst", "type": "address" },

        { "indexed": false, "name": "wad", "type": "uint256" }

      ],

      "name": "Deposit",

      "type": "event"

    },

    {

      "anonymous": false,

      "inputs": [

        { "indexed": true, "name": "src", "type": "address" },

        { "indexed": false, "name": "wad", "type": "uint256" }

      ],

      "name": "Withdrawal",

      "type": "event"

    }

  ]

}

Enter fullscreen mode Exit fullscreen mode

Você também pode copiar o ABI diretamente do Etherscan.

Agora estamos prontos para instanciar o contrato assim:


import { useSigner, useContract } from 'wagmi';

import WETHArtifact from '../utils/abis/WETH.json';

const WETH_ADDRESS = '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6';

const useWETH = () => {

  const { data: signer } = useSigner();

  const WETHContract = useContract({

    address: WETH_ADDRESS,

    abi: WETHArtifact.abi,

    signerOrProvider: signer,

  });

};

export default useWETH;

Enter fullscreen mode Exit fullscreen mode

Acrescentamos uma constante adicional na parte superior, que representa o número de casas decimais do token WETH.


const WETH_DECIMALS = 18;

Enter fullscreen mode Exit fullscreen mode

Em seguida, criamos uma função chamada deposit que analisa o valor da entrada e chama a função de depósito no contrato WETH. Com a ajuda dessa função, convertemos nossa ETH nativa em tokens WETH que podemos usar para troca de tokens.


const deposit = async (amount: number) => {

  if (!WETHContract)

    throw new Error('WETH contract has not been initialized');

  const parsedAmount = ethers.utils.parseUnits(

    amount.toString(),

    WETH_DECIMALS

  );

  const txn = await WETHContract.deposit({ value: parsedAmount });

  return txn;

};

Enter fullscreen mode Exit fullscreen mode

Agora só precisamos criar mais uma função e temos todos os pré-requisitos para poder executar um swap. Crie a função approve. Para que o contrato do roteador Uniswap acesse nosso WETH, ele precisa ter permissão para fazê-lo.


const approve = async (address: string, amount: number) => {

  if (!WETHContract)

    throw new Error('WETH contract has not been initialized');

  const parsedAmount = ethers.utils.parseUnits(

    amount.toString(),

    WETH_DECIMALS

  );

  const txn = WETHContract.approve(address, parsedAmount);

  return txn;

};

return { deposit, approve };

Enter fullscreen mode Exit fullscreen mode

Dentro da nossa função approve  chamamos a função de aprovação do contrato WETH e definimos quantos tokens o roteador pode acessar.

Agora estamos prontos para escrever a lógica da troca.

Execute o swap

Vá até o arquivo useSwap.tsx e adicione esta importação:


import ISwapRouterArtifact from '@uniswap/v3-periphery/artifacts/contracts/interfaces/ISwapRouter.sol/ISwapRouter.json';

Enter fullscreen mode Exit fullscreen mode

Agora, adicione essa constante à parte superior do arquivo:


const ROUTER_ADDRESS = '0xE592427A0AEce92De3Edee1F18E0157C05861564';

Enter fullscreen mode Exit fullscreen mode

E o seguinte, sob o já existente hook useProvider:


const { address } = useAccount();

const { data: signer } = useSigner();

const routerContract = useContract({

  address: ROUTER_ADDRESS,

  abi: ISwapRouterArtifact.abi,

  signerOrProvider: signer,

});

const { approve, deposit } = useWETH();

Enter fullscreen mode Exit fullscreen mode

Como mudaremos o estado da blockchain, precisamos de um assinante em vez de um provedor para o contrato do roteador. Além disso, importamos as funções que escrevemos no hook useWETH.

Agora, finalmente, crie a função swap que executa o swap.


const swap = async (amount: number) => {

  if (!routerContract)

    throw new Error('Router contract has not been initialized');

  await deposit(amount);

  await approve(ROUTER_ADDRESS, amount);

  const immutables = await getPoolImmutables();

  const parsedAmount = ethers.utils.parseUnits(

    amount.toString(),

    UNI_DECIMALS

  );

  const params = {

    tokenIn: immutables.token1,

    tokenOut: immutables.token0,

    fee: immutables.fee,

    recipient: address,

    deadline: Math.floor(Date.now() / 1000) + 60 * 10,

    amountIn: parsedAmount,

    amountOutMinimum: 0,

    sqrtPriceLimitX96: 0,

  };

  const txn = await routerContract.exactInputSingle(params, {

    gasLimit: ethers.utils.hexlify(700000),

  });

  return txn;

};

Enter fullscreen mode Exit fullscreen mode

Esta função aceita a quantidade em ETH que precisa ser trocada como um argumento. Em seguida, converta ETH em WETH com a função deposit que definimos anteriormente e aprova o roteador Uniswap v3 para acessar exatamente esse valor no WETH com a ajuda da função approve.

Em seguida, obtenha os dados de pool necessários ( endereços de token, taxa ) com  função getPoolImmutables e construa um objeto de params que será passado como argumento para a transação. Finalmente, chamamos a função  exactInputSingle no contrato do roteador Uniswap que executa o swap. Um limite de gás personalizado deve ser definido porque a MetaMask não pode estimar a taxa de gás nesta transação.

Agora você pode adicionar a função swap à instrução de retorno e seu arquivo deve ter a seguinte aparência:


import {

  chainId,

  useAccount,

  useContract,

  useProvider,

  useSigner,

} from 'wagmi';

import IUniswapV3PoolArtifact from '@uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json';

import { ethers } from 'ethers';

import { Token } from '@uniswap/sdk-core';

import { Pool } from '@uniswap/v3-sdk';

import ISwapRouterArtifact from '@uniswap/v3-periphery/artifacts/contracts/interfaces/ISwapRouter.sol/ISwapRouter.json';

import useWETH from './useWETH';

interface Immutables {

  token0: string;

  token1: string;

  fee: number;

}

interface State {

  liquidity: ethers.BigNumber;

  sqrtPriceX96: ethers.BigNumber;

  tick: number;

}

const WETH_DECIMALS = 18;

const UNI_DECIMALS = 18;

// WETH - UNI pool with 0.3% fee

const POOL_ADDRESS = '0x07A4f63f643fE39261140DF5E613b9469eccEC86';

const ROUTER_ADDRESS = '0xE592427A0AEce92De3Edee1F18E0157C05861564';

const useSwap = () => {

  const provider = useProvider();

  const { address } = useAccount();

  const { data: signer } = useSigner();

  const routerContract = useContract({

    address: ROUTER_ADDRESS,

    abi: ISwapRouterArtifact.abi,

    signerOrProvider: signer,

  });

  const { approve, deposit } = useWETH();

  const poolContract = useContract({

    address: POOL_ADDRESS,

    abi: IUniswapV3PoolArtifact.abi,

    signerOrProvider: provider,

  });

  const swap = async (amount: number) => {

    if (!routerContract)

      throw new Error('Router contract has not been initialized');

    await deposit(amount);

    await approve(ROUTER_ADDRESS, amount);

    const immutables = await getPoolImmutables();

    const parsedAmount = ethers.utils.parseUnits(

      amount.toString(),

      UNI_DECIMALS

    );

    const params = {

      tokenIn: immutables.token1,

      tokenOut: immutables.token0,

      fee: immutables.fee,

      recipient: address,

      deadline: Math.floor(Date.now() / 1000) + 60 * 10,

      amountIn: parsedAmount,

      amountOutMinimum: 0,

      sqrtPriceLimitX96: 0,

    };

    const txn = await routerContract.exactInputSingle(params, {

      gasLimit: ethers.utils.hexlify(700000),

    });

    return txn;

  };

  const getQuote = async (amount: number) => {

    const [immutables, state] = await Promise.all([

      getPoolImmutables(),

      getPoolState(),

    ]);

    const tokenA = new Token(chainId.goerli, immutables.token0, UNI_DECIMALS);

    const tokenB = new Token(chainId.goerli, immutables.token1, WETH_DECIMALS);

    const pool = new Pool(

      tokenA,

      tokenB,

      immutables.fee,

      state.sqrtPriceX96.toString(),

      state.liquidity.toString(),

      state.tick

    );

    const outputAmount = amount * parseFloat(pool.token1Price.toFixed(2));

    return outputAmount;

  };

  const getPoolImmutables = async () => {

    if (!poolContract)

      throw new Error('Pool contract has not been initialized');

    const [token0, token1, fee] = await Promise.all([

      poolContract.token0(),

      poolContract.token1(),

      poolContract.fee(),

    ]);

    const immutables: Immutables = {

      token0,

      token1,

      fee,

    };

    return immutables;

  };

  const getPoolState = async () => {

    if (!poolContract)

      throw new Error('Pool contract has not been initialized');

    const [liquidity, slot] = await Promise.all([

      poolContract.liquidity(),

      poolContract.slot0(),

    ]);

    const PoolState: State = {

      liquidity,

      sqrtPriceX96: slot[0],

      tick: slot[1],

    };

    return PoolState;

  };

  return { getQuote, swap };

};

export default useSwap;

Enter fullscreen mode Exit fullscreen mode

Agora vá para o componente SwapCard.tsx, adicione a lógica para salvar o valor da entrada no estado e adicione um botão que inicie o swap com o referido valor. Seu arquivo agora deve ficar assim:


import React from 'react';

import { useAccount } from 'wagmi';

import useSwap from '../hooks/useSwap';

const SwapCard = () => {

  const [quote, setQuote] = React.useState(0);

  const [amount, setAmount] = React.useState(0);

  const { address } = useAccount();

  const { getQuote, swap } = useSwap();

  const onChangeAmountInput = async (

    event: React.ChangeEvent<HTMLInputElement>

  ) => {

    setAmount(parseFloat(event.target.value));

    const quote = await getQuote(parseFloat(event.target.value));

    setQuote(quote);

  };

  const onClickSwapButton = async () => {

    const txn = await swap(amount);

    await txn.wait();

  };

  return (

    <>

      <input

        type="text"

        placeholder="Amount in"

        onChange={onChangeAmountInput}

      />

      <p>ETH</p>

      <input

        type="text"

        placeholder="Amount out"

        disabled

        value={quote === 0 ? '' : quote}

      />

      <p>UNI</p>

      <button disabled={address ? false : true} onClick={onClickSwapButton}>

        Swap

      </button>

    </>

  );

};

export default SwapCard;

Enter fullscreen mode Exit fullscreen mode

Além disso, adicionei o comportamento de que o botão de troca só ficará ativado se uma carteira estiver conectada.

Agora vá até o localhost: 3000 para testá-lo, porque tudo deve estar funcionando agora. Apenas certifique-se de ter ETH suficiente na rede Goerli. 

Se você precisar de mais ETH de teste, vá até uma torneira Goerli como a da Alchemy. Além disso, lembre-se de que pode demorar um pouco para que as três transações sejam mineradas, e que não temos nenhum feedback do usuário sobre o status atual da transação no momento.

Exibir o saldo atual de ETH e UNI

Para ver diretamente quanto ETH você pode trocar e obter algum tipo de feedback sobre o resultado da troca, seria bom exibir o saldo atual. Felizmente, com o wagmi.sh, é incrivelmente fácil fazer isso.

Adicione esta constante ao topo do arquivo SwapCard.tsx:


const UNI_ADDRESS = '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984';

Enter fullscreen mode Exit fullscreen mode

Em seguida, adicione isso abaixo do hook useAccount:


const { data: ETHBalance } = useBalance({

  address,

  watch: true,

});

const { data: UNIBalance } = useBalance({

  address,

  token: UNI_ADDRESS,

  watch: true,

});

Enter fullscreen mode Exit fullscreen mode

 A propriedade watch  garante que nosso saldo esteja sempre atualizado, verificando se o saldo foi alterado após cada novo bloco. Agora, por último, mas não menos importante, adicionamos uma pequena nota no modelo para ambos os tokens como este para ETH:


<p>Balance: {ETHBalance?.formatted}</p>

Enter fullscreen mode Exit fullscreen mode

Agora, reunindo todas as coisas, seu arquivo deve se parecer um pouco com isso:


import React from 'react';

import { useAccount, useBalance } from 'wagmi';

import useSwap from '../hooks/useSwap';

const UNI_ADDRESS = '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984';

const SwapCard = () => {

  const [quote, setQuote] = React.useState(0);

  const [amount, setAmount] = React.useState(0);

  const { address } = useAccount();

  const { data: ETHBalance } = useBalance({

    address,

    watch: true,

  });

  const { data: UNIBalance } = useBalance({

    address,

    token: UNI_ADDRESS,

    watch: true,

  });

  const { getQuote, swap } = useSwap();

  const onChangeAmountInput = async (

    event: React.ChangeEvent<HTMLInputElement>

  ) => {

    setAmount(parseFloat(event.target.value));

    const quote = await getQuote(parseFloat(event.target.value));

    setQuote(quote);

  };

  const onClickSwapButton = async () => {

    const txn = await swap(amount);

    await txn.wait();

  };

  return (

    <>

      <input

        type="text"

        placeholder="Amount in"

        onChange={onChangeAmountInput}

      />

      <p>ETH</p>

      <p>Balance: {ETHBalance?.formatted}</p>

      <input

        type="text"

        placeholder="Amount out"

        disabled

        value={quote === 0 ? '' : quote}

      />

      <p>UNI</p>

      <p>Balance: {UNIBalance?.formatted}</p>

      <button disabled={address ? false : true} onClick={onClickSwapButton}>

        Swap

      </button>

    </>

  );

};

export default SwapCard;

Enter fullscreen mode Exit fullscreen mode

Nosso DApp sem estilo agora deve ficar assim:

Se você tentar trocar tokens agora, verá que, após um swap bem-sucedido, o saldo de ambos os tokens muda adequadamente.

                                 . . .

Parabéns!!!

Você construiu com sucesso um dApp que permite trocar seu ETH por UNI. Com base nisso, você pode torná-lo mais bonito, adicionar validação, feedback do usuário, mais tokens etc. As possibilidades são infinitas.

Este tutorial focou apenas na lógica por trás da troca de tokens. Sem o CSS, parece sem graça, e foi por isso que adicionei estilos para o dApp que construímos neste tutorial.

Lógica tutorial com estilos adicionais ( verifique os links abaixo )

Links

  • Se você quiser conferir o código inteiro, siga em frente aqui.

  • Se você quiser conferir o código inteiro, incluindo estilos e mais algumas adições e ajustes, siga em frente aqui.

  • Se você deseja obter uma demonstração do dApp, incluindo estilos e mais algumas adições e ajustes: implantei-o no Vercel aqui.

Obrigado pela leitura!

O feedback é sempre apreciado!


Este artigo foi escrito por Jonas Wolfram e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.

Latest comments (0)