O Ethereum permite aplicações descentralizadas (dApps) através de smart contracts e de uma blockchain programável. O Web3.js disponibiliza a interface JavaScript para interagir com nós Ethereum. Este guia aborda a criação de aplicações blockchain na perspetiva de um developer sénior.
Porquê Desenvolvimento em Ethereum
O Ethereum oferece capacidades únicas:
- Smart Contracts: Código autoexecutável na blockchain
- Descentralização: Sem um único ponto de falha ou de controlo
- Transparência: Todas as transações verificáveis publicamente
- Dinheiro Programável: Criar aplicações financeiras sem intermediários
- NFTs e Tokens: Criar ativos digitais
Configuração do Ambiente de Desenvolvimento
Instalar Dependências
# Ambiente Node.js
npm init -y
npm install web3 ethers dotenv
Fornecedores de Infraestrutura
Utilize fornecedores como a Infura ou a Alchemy em vez de executar o seu próprio nó:
// .env
INFURA_API_KEY=your_api_key_here
PRIVATE_KEY_1=your_private_key_here
PRIVATE_KEY_2=second_private_key_here
Desenvolvimento Local com Ganache
O Ganache disponibiliza uma blockchain Ethereum local para testes:
# Instalar a CLI do Ganache
npm install -g ganache
# Iniciar blockchain local
ganache --port 8545
Noções Básicas de Web3.js
Ligar à Rede
import { Web3 } from 'web3';
import 'dotenv/config';
// Desenvolvimento vs. Produção
const isProduction = false;
const web3 = new Web3({
provider: `https://${!isProduction ? 'sepolia' : 'mainnet'}.infura.io/v3/${process.env.INFURA_API_KEY}`,
config: {
defaultTransactionType: "0x2", // Transações EIP-1559
},
});
// Verificar ligação
const blockNumber = await web3.eth.getBlockNumber();
console.log('Current block:', blockNumber);
Gestão de Contas
// Criar nova conta
const newAccount = web3.eth.accounts.create();
console.log('Address:', newAccount.address);
console.log('Private Key:', newAccount.privateKey);
// Carregar conta existente a partir de chave privada
const account = web3.eth.accounts.privateKeyToAccount(process.env.PRIVATE_KEY_1);
console.log('Loaded address:', account.address);
// Obter saldo
const balance = await web3.eth.getBalance(account.address);
console.log('Balance:', web3.utils.fromWei(balance, 'ether'), 'ETH');
Efetuar Transações
Enviar ETH
import { Web3 } from 'web3';
const isProduction = false;
const web3 = new Web3({
provider: `https://${!isProduction ? 'sepolia' : 'mainnet'}.infura.io/v3/${process.env.INFURA_API_KEY}`,
config: {
defaultTransactionType: "0x2",
},
});
// Inicializar contas
const sender = web3.eth.accounts.privateKeyToAccount(process.env.PRIVATE_KEY_1);
const receiver = web3.eth.accounts.privateKeyToAccount(process.env.PRIVATE_KEY_2);
async function sendTransaction() {
// Registar links das contas para verificação
console.log(`Sender: https://${isProduction ? '' : 'sepolia.'}etherscan.io/address/${sender.address}`);
console.log(`Receiver: https://${isProduction ? '' : 'sepolia.'}etherscan.io/address/${receiver.address}`);
// Verificar saldo
const balance = await web3.eth.getBalance(sender.address);
console.log(`Sender balance: ${web3.utils.fromWei(balance, 'ether')} ETH`);
// Obter contagem de transações (nonce)
const txCount = await web3.eth.getTransactionCount(sender.address);
// Construir transação
const txObject = {
nonce: web3.utils.toHex(txCount),
to: receiver.address,
value: web3.utils.toWei('0.001', 'ether'),
maxPriorityFeePerGas: 10_000,
maxFeePerGas: 10_000_000_000, // Custo máximo de transação permitido
};
// Assinar transação
const signedTx = await sender.signTransaction(txObject);
// Enviar transação
console.log(`TX: https://${isProduction ? '' : 'sepolia.'}etherscan.io/tx/${signedTx.transactionHash}`);
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
console.log('Transaction confirmed in block:', receipt.blockNumber);
// Verificar novo saldo
const newBalance = await web3.eth.getBalance(receiver.address);
console.log(`Receiver balance: ${web3.utils.fromWei(newBalance, 'ether')} ETH`);
}
sendTransaction();
Interação com Smart Contracts
Compilar Smart Contract
Utilizando o compilador Solidity:
npm install solc
Contrato simples SimpleStorage.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleStorage {
uint256 private storedValue;
event ValueChanged(uint256 newValue);
function set(uint256 value) public {
storedValue = value;
emit ValueChanged(value);
}
function get() public view returns (uint256) {
return storedValue;
}
}
Fazer Deploy do Contrato
import { Web3 } from 'web3';
import fs from 'fs';
import solc from 'solc';
const web3 = new Web3('http://localhost:8545'); // Ganache
// Compilar contrato
const source = fs.readFileSync('SimpleStorage.sol', 'utf8');
const input = {
language: 'Solidity',
sources: {
'SimpleStorage.sol': { content: source },
},
settings: {
outputSelection: {
'*': { '*': ['abi', 'evm.bytecode'] },
},
},
};
const compiled = JSON.parse(solc.compile(JSON.stringify(input)));
const contract = compiled.contracts['SimpleStorage.sol']['SimpleStorage'];
const abi = contract.abi;
const bytecode = contract.evm.bytecode.object;
async function deploy() {
const accounts = await web3.eth.getAccounts();
const SimpleStorage = new web3.eth.Contract(abi);
const deployed = await SimpleStorage
.deploy({ data: bytecode })
.send({
from: accounts[0],
gas: 1500000,
});
console.log('Contract deployed at:', deployed.options.address);
return deployed.options.address;
}
deploy();
Interagir com o Contrato
const contractAddress = '0x...'; // Endereço após deploy
const contract = new web3.eth.Contract(abi, contractAddress);
// Ler valor (gratuito, sem gas)
async function getValue() {
const value = await contract.methods.get().call();
console.log('Stored value:', value);
}
// Escrever valor (custa gas)
async function setValue(newValue) {
const accounts = await web3.eth.getAccounts();
const receipt = await contract.methods
.set(newValue)
.send({
from: accounts[0],
gas: 100000,
});
console.log('Transaction hash:', receipt.transactionHash);
}
// Escutar eventos
contract.events.ValueChanged({
fromBlock: 'latest',
})
.on('data', (event) => {
console.log('Value changed to:', event.returnValues.newValue);
})
.on('error', console.error);
Utilizar a Alternativa Ethers.js
O Ethers.js é outra biblioteca popular com uma API mais limpa:
import { ethers } from 'ethers';
// Ligar ao provider
const provider = new ethers.JsonRpcProvider(
`https://sepolia.infura.io/v3/${process.env.INFURA_API_KEY}`
);
// Criar wallet
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
// Enviar transação
async function sendEther() {
const tx = await wallet.sendTransaction({
to: '0xRecipientAddress',
value: ethers.parseEther('0.01'),
});
console.log('TX Hash:', tx.hash);
const receipt = await tx.wait();
console.log('Confirmed in block:', receipt.blockNumber);
}
// Interação com contrato
const contract = new ethers.Contract(contractAddress, abi, wallet);
await contract.set(42);
const value = await contract.get();
Interação com Tokens ERC-20
const tokenABI = [
'function balanceOf(address owner) view returns (uint256)',
'function transfer(address to, uint256 amount) returns (bool)',
'function decimals() view returns (uint8)',
'function symbol() view returns (string)',
];
const tokenContract = new web3.eth.Contract(tokenABI, tokenAddress);
// Obter informação do token
const symbol = await tokenContract.methods.symbol().call();
const decimals = await tokenContract.methods.decimals().call();
const balance = await tokenContract.methods.balanceOf(walletAddress).call();
console.log(`Balance: ${balance / 10 ** decimals} ${symbol}`);
// Transferir tokens
const amount = BigInt(100) * BigInt(10 ** decimals); // 100 tokens
await tokenContract.methods
.transfer(recipientAddress, amount.toString())
.send({ from: senderAddress, gas: 100000 });
Ambiente de Testes
Utilizar a Testnet Sepolia
- Obter ETH de teste a partir do faucet: https://www.alchemy.com/faucets/ethereum-sepolia
- Mudar para a testnet no seu código:
const isProduction = process.env.NODE_ENV === 'production';
const network = isProduction ? 'mainnet' : 'sepolia';
const provider = `https://${network}.infura.io/v3/${process.env.INFURA_API_KEY}`;
Hardhat para Desenvolvimento
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
npx hardhat init
Configurar hardhat.config.js:
require("@nomicfoundation/hardhat-toolbox");
module.exports = {
solidity: "0.8.19",
networks: {
sepolia: {
url: `https://sepolia.infura.io/v3/${process.env.INFURA_API_KEY}`,
accounts: [process.env.PRIVATE_KEY],
},
},
};
Executar testes:
npx hardhat test
npx hardhat run scripts/deploy.js --network sepolia
Ferramentas Úteis de Desenvolvimento
- Remix IDE: Editor Solidity baseado no browser - https://remix.ethereum.org
- Etherscan: Explorador de blocos - https://etherscan.io
- Ganache: Blockchain local - https://github.com/trufflesuite/ganache-ui
- Infura: Infraestrutura de nós - https://www.infura.io
- Alchemy: API melhorada - https://www.alchemy.com
Considerações de Segurança
- Nunca expor chaves privadas: Utilize variáveis de ambiente
- Validar todas as entradas: Os smart contracts não podem ser alterados após o deploy
- Testar exaustivamente: Bugs custam dinheiro real
- Utilizar contratos auditados: Não reinvente a roda para tokens
- Compreender gas: Otimize para poupar dinheiro aos utilizadores
- Lidar com falhas: As transações podem falhar por muitas razões
Principais Conclusões
- Use primeiro as testnets: Teste sempre na Sepolia antes da mainnet
- Compreender gas: Cada operação custa dinheiro
- Eventos para atualizações de UI: Escute eventos do contrato para atualizações em tempo real
- BigInt para montantes: O Ethereum usa 18 casas decimais; use BigInt
- Gestão segura de chaves: Utilize hardware wallets em produção
- Verificar no Etherscan: Publique o código-fonte para transparência
O desenvolvimento blockchain exige atenção cuidadosa à segurança e aos testes — os erros são permanentes e potencialmente dispendiosos. Comece com testnets e montantes pequenos até ter confiança.