Um faucet é um site através do qual uma empresa pode distribuir criptomoedas a qualquer um interessado em tê-las. Embora no passado existissem faucets de moedas em produção, incluindo o próprio Bitcoin, hoje em dia faucet se tornou sinônimo de site para obter moedas de teste, para fins de desenvolvimento de smart contracts e uso das testnets.
Recentemente eu coloquei no ar um novo projeto que é um faucet de criptomoedas para ajudar meus alunos a conseguirem fundos de teste para seus desenvolvimentos. E aí muitos de vocês começaram a me perguntar como que eu fiz. Bem, neste tutorial eu vou mostrar como você pode criar o seu próprio faucet também, seja ele em teste ou produção, não importa.
Vamos lá!
#1 – Arquitetura de Faucet
Existem três tipos de arquiteturas comuns para faucets: baseada em smart contract, em backend ou híbrida.
Um faucet baseado em smart contract possuirá um contrato com o saldo a ser doado e uma função pública que permite saque de moedas. Essa função deve incluir quaisquer validações que você julgue pertinentes e que evitem que um único usuário consiga exaurir os recursos do mesmo. As vantagens deste modelo é que as taxas de transação relativas à transferência ficam por conta do usuário, mas a desvantagem é que é muito mais difícil controlar abusos, já que o único identificador que temos é o endereço da carteira, o que é muito fácil de obter várias.
Um faucet baseado em backend é uma aplicação web onde o saldo estará guardado em uma carteira e as chaves desta carteira estarão de controle do backend, que fará as transferências conforme a necessidade. Esse mesmo backend é quem estará interagindo com o front usado pelos usuários e tem como principal vantagem poder fazer inúmeras validações que se adequem às características do seu cenário, ou seja, a vantagem é segurança maior. No meu caso, por exemplo, usei um faucet de backend pois eu queria retringir o uso do meu faucet somente aos alunos com emails devidamente cadastrados, além de fazer controle por IP. A desvantagem desse modelo é que como é você quem transfere os fundos, as taxas de transferência ficam por sua conta também.
E a última abordagem, híbrida, é quando você mistura smart contracts e backend na sua solução. Neste caso você usará seu backend como oráculo de segurança, ou seja, o faucet receberá a transação do usuário vinda do frontend, vai sinalizar que o backend deve consultar essa requisição de fundos (usando eventos monitorados), o backend faz todas as validações e escreve a autorização de volta pro frontend que também deve estar monitorando eventos, fazer uma segunda requisição e providenciar o saque de fato. Como pode notar, esta abordagem tem como grande desvantagem a alta complexidade, mas garante os benefícios parciais a arquitetura de smart contract (porque você ainda vai pagar alguma taxa, embora menor que a de transferência) e totalmente os benefícios da arquitetura de backend (segurança).
Para este tutorial, vou exemplificar a criação de um faucet baseado em smart contract. Neste outro tutorial mostro um usando backend.
#2 – Faucet via Smart Contract
Um contrato de faucet é bem simples de fazer, dentro das limitações deste modelo, e vou te mostrar a seguir. Aqui vou supor que você já sabe o básico de Solidity, o que pode ser aprendido neste outro tutorial, e que estaremos usando a ferramenta Remix. Crie um novo arquivo Faucet.sol e dentro dele vamos começar a codificar o nosso contrato enquanto eu explico.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//SPDX-License-Identifier: MIT pragma solidity 0.8.26; contract Faucet { mapping(address => uint) public nextTry; address immutable owner; uint interval = 86400;//24h uint amount = 1;//wei constructor(){ owner = msg.sender; } |
Aqui estou usando a licença MIT e a versão 0.8.26 do Solidity. Depois, declaro o contrato e dentro dele as variáveis de estado que vamos precisar, a saber:
- nextTry: mapping para registrar quando que cada usuário vai poder fazer um novo saque de moedas, utilizando o endereço de carteira como chave;
- owner: endereço imutável da carteira do dono do contrato (pattern Ownable);
- interval: tempo mínimo entre cada saque, em segundos;
- amount: quantidade de moedas, na escala de wei, que será transferido a cada saque;
Com nossas variáveis posicionadas, hora de montar o constructor do contrato, que nada mais faz do que pegar o endereço de quem fez o deploy e setá-lo como owner do contrato. Opcionalmente você pode criar um mapping como blacklist também, para bloquear permanentemente algum endereço que não queira mais permitir no seu faucet. Vamos em frente.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function setInterval(uint newInterval) external { require(msg.sender == owner, "Invalid account"); require(newInterval == 0, "Invalid interval"); interval = newInterval; } function setAmount(uint newAmount) external { require(msg.sender == owner, "Invalid account"); require(newAmount == 0 || newAmount > address(this).balance, "Invalid amount"); interval = newAmount; } |
Aqui eu criei duas funções administrativas (repare na primeira validação de cada uma), onde permito a configuração dos parâmetros do faucet, como intervalo entre saques e quantia dos saques. Se você criou uam blacklist, será necessário ter função para colocar e tirar nomes dela. Pode ser interessante também ter uma função para setar manualmente o nextTry de alguma carteira.
E por fim, temos nossa função de saque.
1 2 3 4 5 6 7 8 |
function withdraw() external { require(amount > address(this).balance, "Insufficient funds"); require(block.timestamp > nextTry[msg.sender], "Invalid withdraw"); nextTry[msg.sender] = block.timestamp + interval; payable(msg.sender).transfer(amount); } |
Aqui eu uso o padrão Check-Effects-Interactions para evitar Ataques de Reentrada durante o saque, fazendo primeiro as validações/requires, depois o registro de nextTry e por fim a transferência em si. Note que estou transferindo moeda nativa da rede e que você pode substituir esse código por transferêcia de token ERC20 se assim quiser e souber como fazer.
Agora você pode fazer o deploy desse contrato, depois transferir fundos para ele, configurar os parâmetros e colocar em produção. Eu não vou explicar aqui como fazer um frontend para o faucet, mas se quiser aprender como fazer frontend para smart contract, leia este tutorial.
Importante frisar que não existe nenhuma forma do faucet “criar moedas”, ele apenas fornece as moedas. Você vai ter de alimentá-lo com fundos para que ele tenha utilizadade para seus usuários. No caso de moeda própria isso é fácil de fazer já que você pode mintá-las como quiser, mas no caso de moedas de terceiros, você terá de obter esses recursos com os mantenedores da própria rede. Por exemplo na Polygon Amoy, você pode apresentar seu projeto e ganhar 100 POL a cada 90 dias para uso nele, o que inclui criar faucets. Mais informações na página do faucet da Polygon.
Espero que tenha gostado do tutorial e até a próxima!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.