Recentemente eu escrevi a primeira parte de um tutorial sobre como criar seu próprio software de carteira de criptomoedas utilizando Node.js. Se você ainda não implementou a primeira etapa, deve fazê-lo antes desse, seguindo estas instruções.
Agora se já implementou a primeira funcionalidade e estrutura geral e está ansioso como eu para terminar o projeto, só seguir abaixo!
#1 – Recuperando sua Wallet
Recuperar uma wallet, tenha ela sido criada em nosso software ou qualquer outro que siga o padrão Ethereum/EVM, é tão simples quanto criar uma nova. Começaremos pelo WalletService.js novamente, onde criaremos e exportaremos uma nova função.
1 2 3 4 5 6 7 8 9 10 |
//WalletService.js function recoverWallet(pkOrMnemonic) { myWallet = pkOrMnemonic.indexOf(" ") !== -1 ? ethers.Wallet.fromPhrase(pkOrMnemonic, provider) : new ethers.Wallet(pkOrMnemonic, provider); return myWallet; } |
Aqui eu deixei duas opções de recuperação: por chave privada (PK) ou por frase mnemônica (mnemonic, as famosas 12-palavras). Tendo qualquer um desses dois eu testo qual deles foi recebido (PK não possui espaço) e chamo a função apropriada de recuperação da carteira usando a classe Wallet do Ethers JS. O resultado eu salvo na variável de módulo que guarda a carteira e retorno ela para que as informações sejam de acesso de quem chamou.
Agora voltamos ao index.js para criar a função recoverWallet() que será chamada na opção 2 do menu.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//index.js function recoverWallet() { console.clear(); rl.question(`What is your private key or menmonic phrase? `, (pkOrMnemonic) => { const myWallet = WalletService.recoverWallet(pkOrMnemonic); myAddress = myWallet.address; console.log(`Your recovered wallet:`); console.log(myAddress); preMenu(); }) } |
Console limpo, perguntamos pela PK ou mnemonic e passamos essa informação para o WalletService fazer a recuperação. Guardamos o endereço da carteira na variável global para uso posterior e apresentamos para o usuário antes de limpar tudo e voltar ao menu.
Nós não estamos expondo na tela a chave privada das contas criadas, mas você pode fazê-lo após criar uma nova conta a fim de fazer esse teste de recuperação. Ou então se possuir uma carteira criada em outro software, como a MetaMask, experimente usar a sua PK ou frase mnemônica e verá que a recuperação funciona.
Agora com a carteira criada ou recuperada, o próximo passo é aprendermos a consultar seu saldo.
#2 – Saldo da sua Carteira
Aqui vamos consultar o saldo da carteira “autenticada” (tendo sido ela criada ou recuperada) na moeda nativa da rede que estamos conectados. Para isso, voltamos ao WalletService.js para implementar a respectiva função. Não esqueça de exportar ela.
1 2 3 4 5 6 7 8 9 10 |
//WalletService.js async function getBalance(address) { const balance = await provider.getBalance(myWallet.address); return { balanceInWei: balance, balanceInEth: ethers.formatEther(balance) } } |
A consulta de saldo de uma carteira na blockchain é bem direta e basta termos em mãos o endereço da carteira a ser consultada. O resultado é o saldo em wei, a menor fração da criptomoeda daquela rede. Este valor nessa escala é importante pois todas as funções nativas da blockchain esperam weis e nunca Ethers ou BNBs. No entanto, para o usuário não é nada prático lidar com estes números gigantescos, então além de retornar o balance em wei, eu vou retornar também ele formatado para a maior escala da sua rede com a função formatEther. Não se preocupe com o “Ether” no nome, se estiver em outra rede, vai funcionar também.
Agora no index.js, vamos criar a getBalance() que será disparada quando a opção 3 do menu for escolhida.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//index.js async function getBalance() { console.clear(); if (!myAddress) { console.log(`You don't have a wallet yet.`); return preMenu(); } const { balanceInEth } = await WalletService.getBalance(myAddress); console.log(`${SYMBOL} ${balanceInEth}`); preMenu(); } |
Nesta função se não tivermos ainda um endereço ela vai gerar um erro e voltar ao menu. Caso tenhamos um endereço já configurado (seja pela criação ou recuperação de uma carteira previamente), nós obtemos o saldo chamando a respectiva função do WalletService e imprimimos essa informação no terminal, na escala de ETH (maior escala da moeda).
Para que você possa testar essa funcionalidade você deve ou recuperar uma carteira sua que já tenha saldo ou então adicionar saldo em uma carteira recém criada usando algum faucet da sua rede. Na rede BSC Testnet (que estou usando) eu costumo pegar fundos nesse faucet aqui.
Agora que já temos como ver o saldo também, que tal aprendermos como enviar ele para outra carteira?
#3 – Transferindo fundos da sua carteira
Essa é de longe a funcionalidade mais complexa de ser desenvolvida, mas a essa altura você já deve estar entendendo bem o modus operandi do nosso software de carteira, então não terá grandes problemas. Primeiro passo: WalletService.js, vamos criar três funções nele, pois são etapas diferentes do envio de fundos que o index terá de chamar mais tarde.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
//WalletService.js function addressIsValid(address) { return ethers.isAddress(address); } async function buildTransaction(toWallet, amountInEth) { const amount = ethers.parseEther(amountInEth); const tx = { to: toWallet, value: amount } const feeData = await provider.getFeeData(); const txFee = 21000n * feeData.gasPrice;//default gas limit para transferências const balance = await provider.getBalance(myWallet.address); if (balance < (amount + txFee)) { return false; } return tx; } function sendTransaction(tx){ return myWallet.sendTransaction(tx); } |
A primeira função, addressIsValid, testa se um endereço é válido, algo super útil para evitar enviar fundos para um endereço inválido.
A segunda função, buildTransaction, recebe o endereço do destinatário e a quantia na escala de ETH. Ela converte a quantia para wei, monta o objeto de transação (tx) e calcula a taxa de transação. O feeData é quanto 1 gás custa na rede atualmente, e 21000n é quanto de gás uma transação de transferência gasta em taxas em média. Multiplicando um pelo outro temos o fee da transação estimado. Mas porque calculamos isso?
Quando vamos fazer uma transferência de criptomoedas temos de ter saldo suficiente para pagar pelo valor a ser transferido mas também as taxas da transação de transferência, caso contrário daria erro ao enviar para a blockchain. Calculando a estimativa de taxas, podemos somar com o valor a ser transferido e ver se o dono da carteira tem fundos suficientes para pagar por isso. Se não tiver, retornamos false. Caso tenha, retornamos a transação (tx) devidamente construída e pronta para ser enviada.
Por fim, a terceira e última função é a que faz a transferência em si, passando o objeto tx para a blockchain com a função sendTransaction do objeto de tipo Wallet do Ethers JS. Talvez você se pergunte onde acontece a assinatura dessa transação com a chave privada da carteira e a resposta é: internamente, por isso que chamamos o sendTransaction a partir do objeto de carteira, o que elimina a necessidade de ter de assinar a tx manualmente.
E agora, como usar estas três funções no index.js? É mais fácil do que foi para programar no WalletService pois aqui temos apenas uma função sendTx que será disparada quando a opção 4 for escolhida no menu.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
//index.js function sendTx() { console.clear(); if (!myAddress) { console.log(`You don't have a wallet yet.`); return preMenu(); } console.log(`Your wallet is ${myAddress}`); rl.question(`To Wallet: `, (toWallet) => { if (!WalletService.addressIsValid(toWallet)) { console.log(`Invalid wallet.`); return preMenu(); } rl.question(`Amount (in ${SYMBOL}): `, async (amountInEth) => { if (!amountInEth) { console.log(`Invalid amount.`); return preMenu(); } const tx = await WalletService.buildTransaction(toWallet, amountInEth); if (!tx) { console.log(`Insufficient balance (amount + fee).`); return preMenu(); } try { const txReceipt = await WalletService.sendTransaction(tx); console.log("Transaction successful: "); console.log(txReceipt); } catch (err) { console.error(err); } return preMenu(); }) }) preMenu(); } |
Começamos com a limpeza e verificações básicas, para em seguida pedir a carteira do destinatário dos fundos. O endereço deste destinatário é testado quanto à sua validade com a primeira das três funções que acabamos de criar no WalletService.js, retornando ao menu caso esteja inválida.
Com o destinatário checado, pedimos a quantia a ser transferida, na escala ETH (maior) e testamos se o usuário digitou algo antes de avançar. Caso a quantia esteja válida, construímos a transação com a segunda função do bloco anterior que vai nos retornar false caso tenha falta de fundos, interrompendo nossa transferência ou o objeto tx devidamente construído e pronto para ser enviado pela terceira e última função.
Essa última chamada eu resolvi colocar dentro de um try/catch pois pode ocorrer algum erro ao enviar para a blockchain e quero capturar as informações dele e jogar no console caso aconteça, sem derrubar nosso software. Caso dê tudo certo, receberemos um recibo da transação (txReceipt), sendo que a informação mais importante deste recibo é o hash da transação, que você pode usar para consultar no explorador de blocos da sua rede (o meu é o testnet.bscscan.com) ou mais tarde, em nosso próprio software, com a funcionalidade que faremos a seguir.
Mas antes de avançar, faça esse teste: crie duas carteiras (ou recupere-as caso já possua) e transfira fundos de uma para a outra, anotando o hash da transação, para verificar tanto pelo block explorer quanto pela última funcionalidade que vamos fazer agora.
#4 – Consultando Transações
E por fim, vamos para o WalletService.js uma última vez para implementar a função que vai verificar o estado de uma transação com base em seu hash. Não esqueça de exportar essa função ao final do módulo.
1 2 3 4 5 6 |
//WalletService.js function getTransaction(hash){ return provider.getTransaction(hash); } |
Agora no index.js novamente, vamos criar a última função, searchTx, que pede o hash ao usuário e faz a consulta.
1 2 3 4 5 6 7 8 9 10 11 12 |
function searchTx() { console.clear(); rl.question(`Your tx hash: `, async (hash) => { const txReceipt = await WalletService.getTransaction(hash); console.log("Transaction receipt: "); console.log(txReceipt); return preMenu(); }) } |
Com esta funcionalidade, agora você pode consultar o recibo de transações que sua carteira fez ou qualquer outra que tiver em mãos.
E com isso finalizamos o desenvolvimento de nosso primeiro software de carteira. Espero que este desenvolvimento tenha lhe ajudado a entender melhor como as carteiras funcionam e quiçá sirva como base para você desenvolver um software real de carteira.
Até a próxima!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.