Recentemente escrevi um tutorial onde ensinei a primeira parte da construção de um bot cripto para a dex (exchange decentralizada) SushiSwap V3. Na primeira parte eu ensinei como preparar o ambiente de desenvolvimento, como estruturar o projeto e a fazer o monitoramento do preço.
Nesta segunda parte a minha missão é lhe ajudar a implementar a compra e venda (swap) de tokens.
Então vamos lá!
#1 – Conectando na Blockchain
O primeiro passo é obter uma conexão do seu bot com a sua carteira de criptomoedas, sendo que neste exemplo estou usando a MetaMask. Quando você criou a sua MetaMask (o que fizemos no passo anterior) você recebeu um endereço público e por dentro da carteira (em Detalhes da Conta, imagem abaixo) você pegou sua chave privada, certo? Estas duas informações devem estar agora no seu arquivo .env, sob as variáveis WALLET e PRIVATE_KEY. Também incluímos no .env uma variável INFURA_API_KEY, CHAIN_ID e NETWORK, que são configurações para o full node de blockchain que vamos nos conectar.
Resumindo: usaremos o full node para conexão na blockchain e a carteira para realizar as transações.
Com estas informações agora podemos fazer a instanciação dos objetos de full node, de carteira e de contratos, como abaixo.
1 2 3 4 5 6 7 |
const provider = new ethers.InfuraProvider(process.env.NETWORK, process.env.INFURA_API_KEY); const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider); const token0 = new ethers.Contract(TOKEN0_ADDRESS, ABI_ERC20, signer); const token1 = new ethers.Contract(TOKEN1_ADDRESS, ABI_ERC20, signer); const amountInWei = ethers.parseEther(process.env.AMOUNT_TO_BUY); |
O primeiro objeto é a conexão com o full node (provider), que aqui estamos usando um nó Infura para Sepolia, com a API Key e Network definidas no .env.
O segundo objeto (signer) é a config da nossa carteira, que será usada para assinar as transações. Ela é inicializada com nossa chave privada (definida no .env) e o provider recém configurado.
Já os dois objetos seguintes são praticamente iguais, pois são objetos de contrato de token, mas cada um está apontado para um token diferente, pois ora precisaremos de um, ora de outro. Mas ambos usarão a mesma carteira e o mesmo ABI nas transações.
Por último, defini a quantidade inicial que será negociado do token 0, na escala de wei (a menor fração em redes EVM).
Agora estamos com tudo configurado para comunicação com a blockchain e podemos iniciar a implementação da lógica do robô.
#2 – Fazendo Swap de Tokens
Agora que já sabemos como fazer a conexão no full node com controle total da nossa carteira cripto, é hora de implementarmos a primeira etapa da função que monta o swap dos tokens, para mais tarde podermos enviar esse swap para o smart contract na blockchain. A função abaixo faz essa primeira parte.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
async function swap(tokenIn, tokenOut, amount) { const data = await getSwap({ chainId: parseInt(process.env.CHAIN_ID), tokenIn, tokenOut, sender: process.env.WALLET, amount, maxSlippage: 0.005, // 0.05% max slippage }); if (data.status !== 'Success') return console.error(data); const { tx, assumedAmountOut } = data; console.log(tx, assumedAmountOut); return assumedAmountOut; } |
Essa função espera que você diga o token de entrada, o token de saída e a quantidade (em wei). Com essas informações, mais algumas configurações presentes no .env, eu uso a função getSwap do Sushi SDK para montar um objeto data que tem várias informações muito importantes para meu swap acontecer com sucesso, dentre elas o objeto tx, com os detalhes da transação que tenho de enviar à blockchain, e o valor assumedAmountOut, com a quantidade de token out que vou receber nesse swap.
Experimente chamar esta função no seu index.js passando o TOKEN0_ADDRESS no primeiro parâmetro, o TOKEN1_ADDRESS no segundo e o seu AMOUNT_TO_BUY no terceiro que você vai ver que ela deve retornar as informações que citei.
Com esta primeira parte funcionando, agora é hora de enviar esse tx para o smart contract da SushiSwap na blockchain-alvo. É importante salientar que todas transações na blockchain exigem o pagamento da taxa de gás, que deve ser paga sempre na moeda local, ETH no caso da Sepolia. Ou seja, apesar de não estar negociando ETHs neste bot, você deve ter ETH na carteira a fim de pagar as taxas, ok?
Volte à função de swap e antes do return dela, escreva o seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
console.log(`Swapping ${tokenIn} > ${tokenOut}...`); const txResponse = await signer.sendTransaction({ to: tx.to, data: tx.data, value: tx.value ? ethers.toBigInt(tx.value) : undefined }) console.log('Tx: ', txResponse.hash); const receipt = await txResponse.wait(); console.log('Receipt: ', receipt); |
Aqui eu envio uma transação para a blockchain com as informações do objeto tx que foi montado com o SDK da Sushi anteriormente. Na resposta eu já tenho o hash da transação, mas basta aguardar mais um pouco e terei o recibo completo, que me permite ir no block explorer e ver se tudo funcionou como deveria, como neste exemplo de transação bem sucedida com este mesmo código.
Agora que temos a função de swap pronta e com a lógica que deixamos na função cycle, assim que o preço cair abaixo do parâmetro de gatilho e se o robô ainda não estiver com sua posição aberta (isOpened false) nós podemos fazer a compra acontecer, já sinalizando que a posição ficou aberta (isOpened true). Por outro lado, se a posição já estiver aberta (isOpened true) e o preço de venda foi superado, então a gente pode fazer o swap de venda, reiniciando a variável de controle para que seja possível o robô entrar em outro ciclo de compra.
Veja abaixo como pode ficar:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
async function cycle() { const price = await monitor(); if (price <= PRICE_TO_BUY && !isOpened) { isOpened = true; amountOut = await swap(TOKEN0_ADDRESS, TOKEN1_ADDRESS, amountInWei); } else if (price >= (PRICE_TO_BUY * PROFITABILITY) && isOpened) { await swap(TOKEN1_ADDRESS, TOKEN0_ADDRESS, amountOut); isApproved = false; isOpened = false; } else console.log("Wait..."); setTimeout(cycle, 10000); } |
Repare como no swap de ida e de volta os tokens são passados em ordem diferente e também que a sua quantidade ora é usada a partir da configuração geral, ora a partir do recebido após o primeiro swap. Já a variável isOpened é usada para controlar se é hora de comprar ou vender, enquanto que a variável isApproved será falada a seguir.
A nossa lógica de swap está pronta para execução, mas calma, porque se usar ela assim, vai ter um erro de autorização, então precisamos escrever mais um pouco de código.
#3 – Aprovando a transferência
Se você enviar a transação do jeito que está acima você vai receber um recibo da transação como retorno, como abaixo.
No entanto, você vai verificar que nada vai acontecer no saldo das moedas da sua carteira. Então experimente pegar o valor do campo hash e colocar na Sepolia Etherscan e verá um erro que indica que internamente o contrato da DEX tentou fazer uma operação transferFrom, nativa dos tokens ERC20 mas falhou.
Mas por que falhou, se o código estava todo correto?
Isso porque a função transferFrom, conforme especificação ERC-20, permite que um contrato faça transferência de fundos de uma conta para outra, no entanto isso requer uma aprovação prévia (allowance) do dono da carteira que terá seus fundos transferidos delegando essa permissão para o contrato que fará a transferência, a DEX neste caso.
Resumindo: a DEX não pode sair transferindo seus tokens sem a sua aprovação prévia e é isso o que aconteceu aqui. Tentamos fazer a transferência, mas mesmo com a chave privada em mãos, sem autorização prévia e específica para o contrato da DEX, não rola.
Para dar esta permissão vamos criar mais uma função em nosso index.js, que vou chamar de approve, já que este é o nome presente na especificação. Se olharmos olharmos a mesma veremos que a função approve do ABI deve ter a seguinte assinatura (em Solidity).
1 2 3 |
function approve(address _spender, uint256 _value) public returns (bool success) |
O primeiro parâmetro é o endereço do contrato que vamos dar a permissão e o segundo parâmetro é a quantidade que vamos autorizar deste token. Esta função approve está presente em todos os contratos de token ERC20, e devemos chamá-la sempre antes de um transferFrom onde vamos gastar/enviar tokens daquele contrato em questão.
Assim, uma versão da função approve no index.js pode ser vista abaixo.
1 2 3 4 5 6 7 8 |
async function approve(tokenContract, amountInWei) { const tx = await tokenContract.approve(process.env.ROUTER_ADDRESS, amountInWei); console.log("Approving at " + tx.hash); await tx.wait(); console.log("Approved!"); } |
Recebemos por parâmetro o objeto do contrato do token e usamos ele para chamar a função approve, passando como primeiro parâmetro quem poderá gastar nosso token (o router da dex) e qual a quantia, aguardando até o final antes de avançar.
Com esta função pronta, agora voltamos ao executeCycle e vamos inserir a lógica de approve sempre antes de um swap.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
async function cycle() { if (!isApproved) { await approve(token0, amountInWei); isApproved = true; } const price = await monitor(); if (price <= PRICE_TO_BUY && !isOpened) { isOpened = true; amountOut = await swap(TOKEN0_ADDRESS, TOKEN1_ADDRESS, amountInWei); await approve(token1, amountOut); } else if (price >= (PRICE_TO_BUY * PROFITABILITY) && isOpened) { await swap(TOKEN1_ADDRESS, TOKEN0_ADDRESS, amountOut); isApproved = false; isOpened = false; } else console.log("Wait..."); setTimeout(cycle, 10000); } |
Repare como programei a função approve e swap de forma que eu consigo usar elas tanto na compra quanto na venda, apenas mudando os endereços dos contratos. Na compra aprovamos o gasto de TOKEN0 e trocamos ele por TOKEN1. Já na venda aprovamos o gasto de TOKEN1 e trocamos ele por TOKEN0.
Não apenas isso, mas também usei da variável de controle isApproved para determinar se já fizemos a aprovação inicial (para compra), pois assim conseguimos deixar pré-aprovado antes da primeira compra acontecer, o que nos trará agilidade quando isso for necessário. O mesmo vale para a aprovação de venda, que eu já deixo pré-aprovada para a dex assim que compro as moedas, a fim de ganhar agilidade na venda.
Claro que por segurança você pode só aprovar imediatamente antes de fazer a transferência de fato, mas neste caso saiba que corre o risco de demorar demais a sua compra e venda, pois serão duas transações para concluir a negociação ao invés de uma.
E com isso finalizamos o nosso bot trader de criptomoedas na dex SushiSwap V3. Quer aprender outro, desta vez para UniSwap? Confira este tutorial. Ou então para PancakeSwap neste outro.
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.