Tudo Parece Certo — Mas a Transação Falha. Parte II

Tudo Parece Certo — Mas a Transação Falha. Parte II

Traduzido do inglês

Na primeira parte deste artigo, descrevi um caso em que o serviço de troca rabbit.io tentou enviar uma pequena quantidade de SOL a um cliente, mas a transação continuava sendo rejeitada — embora, segundo todas as regras conhecidas da blockchain Solana, tudo parecesse perfeitamente válido.

Essa primeira parte do artigo também abordou situações em que uma transação que é tecnicamente correta ainda pode ser rejeitada em redes como XRP Ledger, Stellar e Lightning Network (a Layer 2 do Bitcoin). Também expliquei um detalhe de implementação das listas negras no contrato inteligente do USDC que pode fazer com que transações com USDC falhem inesperadamente, mesmo quando nada parece estar errado do lado do remetente.

Se você perdeu a Parte I, pode lê-la aqui.

Hoje quero analisar vários exemplos semelhantes envolvendo outras redes e tokens. Em cada um deles, tudo pode parecer correto à primeira vista — ainda assim a transação falha.

5. USDT em DeFi. A Transação Reverte Silenciosamente

Este caso é específico para desenvolvedores e operadores de protocolos DeFi. No entanto, usuários comuns também podem sentir suas consequências.

Imagine a seguinte situação. Você está tentando depositar USDC, DAI e USDT em algum protocolo. Os depósitos com USDC e DAI funcionam bem, mas a transferência de USDT é revertida. O endereço está correto, a rede está correta e há gás suficiente. Qual é a razão?

A razão é que o USDT no Ethereum não cumpre plenamente o padrão ERC-20. O padrão exige que as funções transfer e transferFrom retornem um valor booleano: true em caso de sucesso e false em caso de falha. O USDT não retorna nada.

Se um contrato inteligente for escrito estritamente de acordo com o padrão ERC-20 e esperar receber um valor booleano em resposta à chamada transfer, enquanto o USDT não retorna nada, a máquina virtual EVM (ao usar Solidity a partir da versão 0.4.22) interpreta isso como um erro e reverte a transferência. Tecnicamente a transação poderia ter ocorrido com sucesso, mas é forçosamente revertida. E o remetente não recebe uma indicação clara, nem da rede nem do contrato inteligente, sobre o que exatamente deu errado.

Este exemplo não tem relação com swaps no rabbit.io. Nosso sistema é organizado da forma mais simples e confiável possível: você recebe um endereço para o qual envia seus tokens manualmente e, em troca, recebe os tokens que precisa de nós. Contratos inteligentes DeFi não estão envolvidos.

No entanto, para leitores que não apenas realizam swaps no rabbit.io, mas também interagem ativamente com DeFi — incluindo desenvolvedores — posso sugerir uma solução simples. Use a biblioteca SafeERC20 da OpenZeppelin, que lida corretamente com tokens não padronizados.

A maioria dos protocolos DeFi profissionalmente escritos já faz isso. Contudo, contratos inteligentes mais antigos ou amadores ainda quebram ao interagir com o USDT. Esse problema é tão difundido que foi incluído no banco de dados weird-erc20 de peculiaridades conhecidas de tokens, onde você também pode encontrar outros tokens com comportamento semelhante.

6. USDT no Ethereum. Allowances Não Podem Ser Alteradas Diretamente

O USDT no Ethereum tem outra peculiaridade que regularmente causa falhas em transações com contratos inteligentes.

No padrão ERC-20, se você quer permitir que um contrato inteligente gaste seus tokens, você chama a função approve(spender, amount). Por exemplo, se você deseja aumentar a allowance de 100 para 200 USDC, basta chamar approve(spender, 200).

O USDT funciona de forma diferente. Seu código declara explicitamente que se o endereço receptor já tem uma allowance não zero, não é permitido definir diretamente um novo valor não zero. Uma transação que seria completamente válida para quase qualquer outro token é rejeitada quando usada com USDT.

Esse comportamento foi introduzido como proteção contra um ataque de double-spend envolvendo allowances. Os desenvolvedores temiam que um atacante pudesse gastar instantaneamente tanto a allowance antiga quanto a nova.

O padrão obrigatório para alterar uma allowance de USDT é, portanto, o seguinte:

  • Passo 1: chamar approve(spender, 0) para resetar a allowance
  • Passo 2: aguardar a confirmação da transação
  • Passo 3: chamar approve(spender, new_value)

Para usuários de aplicações descentralizadas isso pode ser confuso. O usuário anteriormente concedeu uma allowance à aplicação, agora quer definir uma allowance diferente, assina a transação — e nada muda sem uma explicação óbvia.

Este exemplo também não tem relação com swaps no rabbit.io. Tais transações rejeitadas não podem ocorrer em nossa plataforma porque rabbit.io não exige conectar uma carteira e não solicita permissão para gastar tokens dela.

No entanto, se em alguma dApp sua transação approve envolvendo USDT falhar, a solução é geralmente simples. Verifique o nível atual da allowance. Se não for zero, primeiro redefina-a para zero e só então defina o novo valor.

7. Bitcoin. O Limite de Dust

Na rede Bitcoin existe um conceito chamado dust. Refere-se a uma saída de transação cujo valor é menor do que o montante necessário para gastá-la mais tarde. Em outras palavras, as moedas tecnicamente existem na sua carteira, mas a taxa necessária para enviá-las seria maior do que as próprias moedas.

É importante entender uma distinção fundamental que costuma ser confundida: a diferença entre regras de consenso e política de nó.

Uma transação cujas saídas são menores que o limite de dust pode ser tecnicamente válida segundo as regras de consenso do Bitcoin. Contudo, a maioria dos nós, por padrão, recusará relayerizar tal transação, e a maioria dos mineradores se recusará a incluí-la em um bloco.

A razão é simples: armazenar moedas extremamente pequenas sobrecarrega o banco de dados sem fornecer qualquer benefício econômico real.

O limite de dust depende do tipo de endereço e da taxa mínima de relay. Historicamente o limite de dust é considerado 546 satoshis. Transações com saídas menores custariam mais em taxas do que o valor enviado se a taxa mínima for 1 sat/vByte. Mas isso se aplica apenas a endereços legacy que começam com "1".

Tipos de endereço mais modernos criam transações que ocupam menos espaço na blockchain, então seus limites de dust podem ser menores. Por exemplo, para endereços regulares bc1q... o limite de dust a uma taxa de 1 sat/vByte é 294 satoshis, e com a taxa mínima de relay atualmente aplicada (0.1 sat/vByte) o limite de dust fica dez vezes menor.

Bitcoin fees, Source: mempool.space

Imagine a seguinte situação.

Você tem 100.000 satoshis (0.001 BTC). Quer enviar 99.000 satoshis para alguém. Você constrói uma transação, e a taxa de rede é de 980 satoshis. Os 20 satoshis restantes deveriam voltar para você como troco.

Mas isso não acontecerá. Você não receberá seu troco, e o pagamento principal também não chegará ao destinatário. A rede de nós de relay rejeitará silenciosamente a transação porque uma de suas saídas está abaixo do limite de dust.

Há também um problema relacionado: gastar dust mais tarde. Se você receber muitas saídas menores que 294 satoshis enquanto as taxas estiverem baixas, e posteriormente as taxas aumentarem, qualquer transação tentando gastar essas moedas pode se tornar tão cara que o valor transferido nem sequer cubra a taxa necessária.

Portanto, ao trocar outros criptoativos por bitcoin, certifique-se de que as saídas criadas excedam o limite de dust. Rabbit.io permite swaps em valores várias vezes superiores ao limite de dust atual. No entanto, se as taxas de rede aumentarem, quantias tão pequenas podem se tornar difíceis de enviar adiante.

8. Ethereum e Redes EVM. Uma Transação Antiga Bloqueia Todas as Novas

No Ethereum e em qualquer blockchain compatível com EVM (como BSC, HyperEVM e outras), cada transação enviada de um determinado endereço tem um número sequencial chamado nonce.

A rede processa transações estritamente em ordem: primeiro a transação com nonce = 0, depois nonce = 1, depois 2, e assim por diante. É impossível pular um número.

Se uma transação ficar presa no mempool devido a uma taxa de gás muito baixa, todas as transações subsequentes do mesmo endereço com nonces maiores também permanecerão pendentes, mesmo que tenham taxas normais.

Aqui está um cenário muito realista. Há algumas semanas você enviou uma transação com uma taxa de gás baixa. Ela ficou presa. Você esqueceu dela. Agora tenta enviar uma nova transação. Especifica o endereço correto e define uma taxa adequada, mas a transação continua falhando em ser processada.

O problema não é a transação atual, mas a antiga pendente.

A solução é enviar uma transação com o mesmo nonce antigo, mas com uma taxa de gás mais alta. Isso acelerará a transação original (speed up) ou a cancelará. O último caso acontece se você enviar uma transferência para si mesmo com o mesmo nonce e uma taxa de gás mais alta.

No entanto, note que nem todas as carteiras permitem que os usuários controlem o valor do nonce. Por exemplo, carteiras populares como Trust Wallet e Exodus não fornecem essa função. Enquanto isso, o MetaMask — que muitas pessoas consideram desatualizado e inconveniente — na verdade permite.

9. TRON e USDT TRC-20: Falha por Falta de Energy

A rede TRON tem um modelo de recursos incomum. Em vez de uma única taxa de transação, as transações consomem dois tipos de recursos:

  • Bandwidth — para qualquer tipo de transação
  • Energy — para interações com contratos inteligentes, incluindo transferências de tokens TRC-20

A energia pode ser obtida ao congelar TRX. Se isso não for feito, a rede automaticamente queima TRX para cobrir o custo.

E é aqui que surge um problema inesperado de precificação:

  • se o destinatário já teve USDT antes, cerca de 6–13 TRX serão queimados
  • se o destinatário nunca teve USDT antes, 13–27 TRX podem ser necessários

Se o remetente tem 15 TRX disponíveis para taxas mas a transferência vai para um endereço novo, a transação pode falhar com o erro OUT OF ENERGY. Os TRX gastos nessa tentativa não são reembolsados.

Em outras palavras, a situação novamente se parece com os outros exemplos deste artigo: o endereço está correto, a rede está correta, o remetente tem USDT, TRX estava presente — ainda assim a transação falha.

A conclusão é simples: para um trabalho estável com USDT TRC-20, é recomendável manter pelo menos 27 TRX no saldo para cobrir possíveis custos de transação.

Em vez de Conclusão

A história com a transferência de SOL que falhou me ensinou uma lição valiosa. Não no sentido de que eu tenha entendido mal blockchains, mas no sentido de que cada blockchain é seu próprio ecossistema com regras ocultas que nem sempre estão claramente documentadas e às vezes se revelam apenas em situações incomuns.

Cada falha confusa não é um motivo para frustração, mas uma oportunidade para aprender algo novo. E quanto mais coisas novas eu aprendo, mais interessante fica para mim continuar trabalhando com criptomoedas.

Se você sente o mesmo, documente casos incomuns e compartilhe suas descobertas. A indústria cripto fica mais confiável quando a experiência prática se espalha mais rápido do que a documentação oficial pode ser atualizada.