Ethers.js Tingkat Lanjut: Menguasai Interaksi Smart Contract dan Transaksi Ethereum di Aplikasi Web Anda
1. Pendahuluan
Dunia Web3 dan aplikasi terdesentralisasi (DApps) berkembang pesat, dan Ethereum menjadi salah satu fondasi utamanya. Bagi developer web, berinteraksi dengan blockchain Ethereum dan smart contract-nya adalah keahlian krusial. Di sinilah library seperti Ethers.js berperan. Ethers.js adalah library JavaScript yang kuat dan ringan untuk berinteraksi dengan Ethereum, menawarkan API yang bersih dan mudah digunakan.
Meskipun artikel sebelumnya telah membahas dasar-dasar menghubungkan frontend ke smart contract, interaksi dengan blockchain jauh lebih kompleks daripada sekadar memanggil fungsi. Kita perlu memahami bagaimana mengelola dompet pengguna, mengirim transaksi yang aman, mengestimasi biaya gas, dan bahkan memantau event yang terjadi di blockchain secara real-time.
Artikel ini akan membawa Anda menyelam lebih dalam ke Ethers.js. Kita akan menjelajahi konsep inti seperti Provider, Signer, dan Contract secara lebih detail, serta mempelajari praktik terbaik dalam mengelola dompet, membaca dan menulis data ke smart contract, hingga mendengarkan event. Tujuannya adalah membekali Anda dengan pengetahuan praktis untuk membangun DApps yang lebih robust dan interaktif.
📌 Mengapa Ethers.js? Ethers.js dikenal karena fokusnya pada keamanan, desain modular, dan API yang developer-friendly. Ini adalah pilihan populer di kalangan developer Web3 karena memberikan kontrol penuh dan abstraksi yang tepat untuk berinteraksi dengan node Ethereum.
2. Fondasi Ethers.js: Provider, Signer, dan Contract
Untuk berinteraksi dengan blockchain Ethereum menggunakan Ethers.js, kita perlu memahami tiga komponen inti: Provider, Signer, dan Contract. Mari kita analogikan ini dengan sebuah bank:
-
Provider(Petugas Informasi Bank): Ini adalah koneksi read-only Anda ke blockchain. Ibarat petugas informasi di bank, ia bisa memberi tahu Anda saldo rekening seseorang, riwayat transaksi, atau isi brankas tanpa bisa mengubah apa pun.- Tugas: Membaca data state blockchain, saldo ETH, informasi transaksi, data smart contract (fungsi
view/pure). - Contoh:
ethers.providers.JsonRpcProvider,ethers.providers.Web3Provider(untuk MetaMask).
- Tugas: Membaca data state blockchain, saldo ETH, informasi transaksi, data smart contract (fungsi
-
Signer(Pemegang Kunci Brankas): Ini adalah abstraksi dari dompet Ethereum yang dapat menandatangani transaksi dan pesan. Ibarat pemegang kunci brankas, ia memiliki otorisasi untuk melakukan perubahan (mengirim uang, mengubah data).- Tugas: Menandatangani transaksi (mengirim ETH, memanggil fungsi smart contract yang mengubah state), menandatangani pesan.
- Contoh:
provider.getSigner()(dariWeb3Provideryang terhubung ke MetaMask),new ethers.Wallet(privateKey, provider).
-
Contract(Formulir Transaksi Khusus): Ini adalah objek JavaScript yang merepresentasikan smart contract di blockchain. Ibarat formulir khusus untuk layanan bank tertentu (misal, formulir pembukaan rekening atau transfer dana), ia tahu fungsi-fungsi yang bisa dipanggil dan cara berinteraksi dengannya.- Tugas: Memanggil fungsi smart contract, mendengarkan event contract.
- Diperlukan: Alamat contract (address), Application Binary Interface (ABI) contract.
import { ethers } from "ethers";
// 1. Inisialisasi Provider (Baca data blockchain)
// Untuk MetaMask/browser wallet:
const provider = new ethers.providers.Web3Provider(window.ethereum);
// Untuk node RPC (misal Infura, Alchemy):
// const provider = new ethers.providers.JsonRpcProvider("https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID");
// 2. Inisialisasi Signer (Menulis data ke blockchain)
// Mengambil signer dari provider yang terhubung ke MetaMask
const signer = provider.getSigner();
// Atau membuat signer dari private key (HATI-HATI: JANGAN DI PRODUKSI DI FRONTEND!)
// const privateKeySigner = new ethers.Wallet("0x...", provider);
// 3. Inisialisasi Contract (Berinteraksi dengan smart contract)
const contractAddress = "0x..."; // Alamat smart contract Anda
const contractABI = [ /* ABI contract Anda dalam format JSON */ ]; // ABI contract Anda
const myContract = new ethers.Contract(contractAddress, contractABI, provider); // Hanya bisa membaca
const myContractWithSigner = new ethers.Contract(contractAddress, contractABI, signer); // Bisa membaca dan menulis
console.log("Provider siap:", provider);
console.log("Signer siap:", signer);
console.log("Contract siap:", myContract);
💡 Tips: Selalu gunakan Web3Provider saat berinteraksi dengan dompet di browser (misalnya MetaMask) agar pengguna dapat menyetujui transaksi. JsonRpcProvider lebih cocok untuk backend atau aplikasi yang hanya membaca data.
3. Mengelola Dompet dan Kunci Privat dengan Aman
Ethers.js menyediakan utilitas lengkap untuk mengelola dompet Ethereum. Namun, keamanan kunci privat adalah prioritas utama.
import { ethers } from "ethers";
// ✅ Membuat dompet baru secara programatik
const wallet = ethers.Wallet.createRandom();
console.log("Alamat dompet baru:", wallet.address);
console.log("Kunci privat (HATI-HATI!):", wallet.privateKey);
console.log("Seed Phrase (Mnemonic):", wallet.mnemonic.phrase);
// ✅ Mengimpor dompet dari kunci privat
const importedWallet = new ethers.Wallet("0xabc123...", provider); // Ganti dengan private key asli
console.log("Alamat dompet impor:", importedWallet.address);
// ✅ Mengimpor dompet dari mnemonic/seed phrase
const hdNodeWallet = ethers.Wallet.fromMnemonic("word1 word2 word3 ..."); // Ganti dengan mnemonic asli
console.log("Alamat dompet dari Mnemonic:", hdNodeWallet.address);
// ⚠️ Peringatan Keamanan:
// JANGAN PERNAH menyimpan kunci privat atau mnemonic secara langsung di frontend
// atau di kode yang akan diakses publik. Ini adalah risiko keamanan yang sangat besar!
// Gunakan dompet browser seperti MetaMask untuk menangani kunci privat pengguna.
// Jika harus mengelola kunci privat di backend, pastikan disimpan dengan sangat aman
// (misalnya menggunakan HashiCorp Vault atau AWS Secrets Manager).
// ✅ Mengenkripsi dompet untuk penyimpanan aman (misalnya di backend)
async function encryptAndDecryptWallet() {
const password = "password_super_rahasia";
const encryptedJson = await wallet.encrypt(password);
console.log("Dompet terenkripsi (JSON):", encryptedJson);
// Untuk mendekripsi:
const decryptedWallet = await ethers.Wallet.fromEncryptedJson(encryptedJson, password);
console.log("Dompet terdekripsi:", decryptedWallet.address);
}
// encryptAndDecryptWallet();
Kunci privat adalah satu-satunya bukti kepemilikan aset di blockchain. Kehilangan kunci privat berarti kehilangan aset Anda. Pastikan Anda dan pengguna Anda selalu berhati-hati.
4. Membaca Data dari Smart Contract (Call)
Berinteraksi dengan fungsi smart contract yang hanya membaca data (ditandai view atau pure di Solidity) adalah operasi yang paling umum dan gratis di Ethereum.
import { ethers } from "ethers";
// Asumsikan provider sudah diinisialisasi
const provider = new ethers.providers.JsonRpcProvider("https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID");
const contractAddress = "0x... (alamat contract ERC-20 token)"; // Contoh contract address
const contractABI = [
"function name() view returns (string)",
"function symbol() view returns (string)",
"function totalSupply() view returns (uint256)",
"function balanceOf(address owner) view returns (uint256)"
];
// Inisialisasi contract dengan provider (hanya membaca)
const erc20Contract = new ethers.Contract(contractAddress, contractABI, provider);
async function readContractData() {
try {
const name = await erc20Contract.name();
const symbol = await erc20Contract.symbol();
const totalSupply = await erc20Contract.totalSupply();
console.log(`Nama Token: ${name}`);
console.log(`Simbol Token: ${symbol}`);
console.log(`Total Supply: ${ethers.utils.formatUnits(totalSupply, 0)}`); // Format totalSupply dari BigNumber
const userAddress = "0x... (alamat pengguna)"; // Ganti dengan alamat pengguna
const balance = await erc20Contract.balanceOf(userAddress);
console.log(`Saldo ${userAddress}: ${ethers.utils.formatEther(balance)} ${symbol}`); // Format saldo ke ETH (atau unit token yang sesuai)
} catch (error) {
console.error("❌ Gagal membaca data contract:", error);
}
}
// readContractData();
✅ Praktik Terbaik: Gunakan ethers.utils.formatUnits dan ethers.utils.parseUnits untuk mengonversi nilai BigNumber (yang digunakan Ethers.js untuk representasi angka besar) ke format yang mudah dibaca dan sebaliknya, dengan memperhatikan desimal token.
5. Mengirim Transaksi dan Memodifikasi State (Send)
Mengirim transaksi adalah operasi yang mengubah state blockchain dan memerlukan biaya gas. Ini melibatkan Signer untuk menandatangani transaksi.
import { ethers } from "ethers";
// Asumsikan provider dan signer sudah diinisialisasi
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const tokenContractAddress = "0x... (alamat contract ERC-20 token)";
const tokenContractABI = [
"function transfer(address to, uint256 amount) returns (bool)",
"function approve(address spender, uint256 amount) returns (bool)",
"function decimals() view returns (uint8)"
];
const tokenContractWithSigner = new ethers.Contract(tokenContractAddress, tokenContractABI, signer);
async function sendTransaction() {
try {
const recipientAddress = "0x... (alamat penerima)";
const amountToSend = "100"; // Jumlah token yang ingin dikirim
// Ambil jumlah desimal token untuk parsing yang benar
const decimals = await tokenContractWithSigner.decimals();
const parsedAmount = ethers.utils.parseUnits(amountToSend, decimals);
// 🎯 Estimasi Gas (opsional tapi sangat direkomendasikan)
// Ini membantu memberi tahu pengguna perkiraan biaya transaksi
const gasEstimate = await tokenContractWithSigner.estimateGas.transfer(recipientAddress, parsedAmount);
console.log(`Estimasi Gas: ${gasEstimate.toString()}`);
// Kirim transaksi transfer token
const tx = await tokenContractWithSigner.transfer(recipientAddress, parsedAmount, {
gasLimit: gasEstimate.mul(120).div(100) // Tambahkan buffer 20% untuk gas limit
});
console.log("Transaksi dikirim:", tx.hash);
console.log("Menunggu konfirmasi...");
// Menunggu transaksi dikonfirmasi di blockchain
const receipt = await tx.wait();
console.log("✅ Transaksi dikonfirmasi di blok:", receipt.blockNumber);
console.log("Detail Receipt:", receipt);
} catch (error) {
console.error("❌ Gagal mengirim transaksi:", error);
// Tangani error spesifik, misal:
if (error.code === 4001) {
console.warn("Pengguna menolak transaksi.");
} else if (error.code === "INSUFFICIENT_FUNDS") {
console.warn("Dana tidak cukup untuk gas atau transaksi.");
}
}
}
// sendTransaction();
// 💡 Mengirim ETH langsung (bukan token contract)
async function sendEth() {
try {
const recipientAddress = "0x... (alamat penerima ETH)";
const amountEth = "0.01"; // Jumlah ETH yang ingin dikirim
const tx = await signer.sendTransaction({
to: recipientAddress,
value: ethers.utils.parseEther(amountEth) // Mengonversi ke wei (smallest unit of ETH)
});
console.log("Transaksi ETH dikirim:", tx.hash);
await tx.wait();
console.log("✅ Transaksi ETH dikonfirmasi.");
} catch (error) {
console.error("❌ Gagal mengirim ETH:", error);
}
}
// sendEth();
⚠️ Penting: Selalu pertimbangkan gasLimit dan gasPrice (atau maxFeePerGas/maxPriorityFeePerGas di EIP-1559) saat mengirim transaksi. Ethers.js seringkali bisa mengestimasi gasLimit secara otomatis, tetapi menambahkan buffer kecil (misal 20%) adalah praktik yang baik untuk menghindari transaksi gagal karena gas tidak cukup.
6. Memantau Event dari Smart Contract
Smart contract dapat mengeluarkan event untuk memberi tahu dunia luar tentang perubahan state. Ethers.js memungkinkan kita untuk mendengarkan event ini secara real-time.
import { ethers } from "ethers";
// Asumsikan provider sudah diinisialisasi
const provider = new ethers.providers.JsonRpcProvider("wss://mainnet.infura.io/ws/v3/YOUR_INFURA_PROJECT_ID");