Menghubungkan Frontend Web Anda ke Smart Contract Ethereum: Panduan Praktis dengan Ethers.js dan React
1. Pendahuluan
Selamat datang kembali di blog saya! Jika Anda pernah membaca artikel saya tentang “Memulai Pengembangan DApps dengan Ethereum dan Solidity: Panduan Praktis untuk Developer Web”, Anda mungkin sudah familiar dengan konsep dasar smart contract dan bagaimana cara menulisnya. Tapi, apa gunanya smart contract yang powerful jika tidak ada cara bagi pengguna untuk berinteraksi dengannya? Di sinilah peran frontend web menjadi krusial.
Membangun aplikasi terdesentralisasi (DApp) bukan hanya tentang menulis kode Solidity yang efisien, tetapi juga tentang menciptakan pengalaman pengguna (UX) yang mulus agar mereka bisa dengan mudah mengirim transaksi, membaca data, dan merasakan manfaat blockchain. Artikel ini akan menjadi jembatan antara smart contract yang Anda deploy dengan aplikasi web interaktif yang Anda bangun.
Kita akan fokus pada bagaimana frontend web, khususnya dengan framework React, dapat berkomunikasi langsung dengan smart contract Ethereum. Kita akan menggunakan Ethers.js, sebuah library JavaScript yang ringan dan powerful, sebagai alat utama kita untuk berinteraksi dengan blockchain. Ini adalah panduan praktis yang akan membekali Anda dengan pengetahuan esensial untuk membangun DApp yang fungsional dan responsif.
Mari kita mulai petualangan kita di dunia Web3!
2. Memahami Jembatan Web2 ke Web3: Wallet dan Provider
Sebelum frontend kita bisa “berbicara” dengan smart contract, ada dua komponen penting yang harus dipahami: Wallet (Dompet) dan Provider.
Wallet (Dompet)
📌 Analoginya: Bayangkan dompet Anda di dunia nyata. Di dalamnya ada uang dan kartu identitas. Di dunia Web3, dompet seperti MetaMask atau WalletConnect memegang kunci privat Anda (yang mengontrol aset kripto dan identitas blockchain Anda) dan memungkinkan Anda menandatangani transaksi. Tanpa dompet, Anda tidak bisa melakukan apa pun yang mengubah state blockchain.
Provider
💡 Analoginya: Jika dompet adalah uang Anda, maka Provider adalah bank atau node blockchain yang Anda gunakan untuk mengirim dan menerima informasi. Provider adalah koneksi kita ke jaringan Ethereum (atau jaringan EVM-compatible lainnya). Ethers.js akan menggunakan Provider untuk:
- Membaca data dari blockchain (misalnya, saldo ETH, nilai variabel di smart contract).
- Mengirim transaksi yang sudah ditandatangani oleh dompet pengguna ke jaringan.
Di frontend, kita biasanya menggunakan window.ethereum yang disediakan oleh ekstensi dompet seperti MetaMask sebagai Provider.
Berikut adalah langkah pertama untuk menginisialisasi Provider dan meminta koneksi ke dompet pengguna di aplikasi React Anda:
import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers';
function DAppConnector() {
const [provider, setProvider] = useState(null);
const [signer, setSigner] = useState(null);
const [account, setAccount] = useState(null);
const [network, setNetwork] = useState(null);
const [error, setError] = useState('');
// Fungsi untuk menghubungkan dompet
const connectWallet = async () => {
setError('');
try {
// Cek apakah MetaMask terinstal
if (!window.ethereum) {
setError("MetaMask tidak terinstal. Silakan instal terlebih dahulu.");
return;
}
// Inisialisasi Provider dari MetaMask
const web3Provider = new ethers.BrowserProvider(window.ethereum);
setProvider(web3Provider);
// Meminta pengguna untuk menghubungkan akun mereka
const accounts = await web3Provider.send("eth_requestAccounts", []);
setAccount(accounts[0]); // Ambil akun pertama
// Dapatkan Signer (untuk menandatangani transaksi)
const web3Signer = await web3Provider.getSigner();
setSigner(web3Signer);
// Dapatkan informasi jaringan
const network = await web3Provider.getNetwork();
setNetwork(network.name);
console.log("Dompet terhubung:", accounts[0], "di jaringan:", network.name);
} catch (err) {
console.error("Gagal menghubungkan dompet:", err);
setError("Gagal menghubungkan dompet. Pastikan Anda memberikan izin.");
}
};
// Efek untuk memuat saat komponen pertama kali dirender
useEffect(() => {
// Anda bisa memanggil connectWallet di sini jika ingin otomatis terhubung
// atau biarkan pengguna mengklik tombol
}, []);
return (
<div>
<h1>DApp Connector</h1>
{!account ? (
<button onClick={connectWallet}>Hubungkan Dompet</button>
) : (
<div>
<p>✅ Dompet Terhubung!</p>
<p>Alamat: {account}</p>
<p>Jaringan: {network}</p>
</div>
)}
{error && <p style={{ color: 'red' }}>⚠️ {error}</p>}
</div>
);
}
export default DAppConnector;
Dalam kode di atas, BrowserProvider dari Ethers.js mengambil window.ethereum dan signer adalah objek yang mewakili akun pengguna yang terhubung, yang akan kita gunakan untuk menandatangani transaksi.
3. Berinteraksi dengan Smart Contract: ABI dan Alamat
Setelah kita berhasil terhubung dengan dompet dan Provider, langkah selanjutnya adalah memberi tahu Ethers.js “kontrak mana yang ingin kita ajak bicara dan bagaimana cara bicaranya”. Untuk ini, kita butuh dua hal:
- Contract Address (Alamat Kontrak): Ini adalah alamat unik di blockchain tempat smart contract Anda di-deploy.
- ABI (Application Binary Interface): Ini adalah “manual instruksi” kontrak Anda. ABI adalah representasi JSON dari semua fungsi dan event publik yang ada di smart contract Anda. Ini memungkinkan Ethers.js tahu fungsi apa yang bisa dipanggil dan parameter apa yang dibutuhkan.
Anda bisa mendapatkan ABI setelah mengkompilasi smart contract Solidity Anda. Biasanya, file ABI berada di folder artifacts atau build proyek development Anda (misalnya, Hardhat atau Foundry).
import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers';
// Import ABI dari file JSON yang dihasilkan setelah kompilasi smart contract
// Ganti dengan path ke ABI smart contract Anda
import MyContractABI from './MyContract.json'; // Misal: { abi: [...], bytecode: '...' }
// Alamat smart contract yang sudah di-deploy
const CONTRACT_ADDRESS = "0xYourDeployedContractAddressHere"; // Ganti dengan alamat kontrak Anda
function ContractInteraction({ provider, signer, account }) {
const [contract, setContract] = useState(null);
const [dataFromContract, setDataFromContract] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
useEffect(() => {
if (provider && account) {
try {
// Buat instance kontrak
// Jika hanya ingin membaca data (read-only), cukup gunakan provider
const contractInstance = new ethers.Contract(CONTRACT_ADDRESS, MyContractABI.abi, provider);
setContract(contractInstance);
console.log("Kontrak berhasil diinisialisasi.");
} catch (err) {
console.error("Gagal inisialisasi kontrak:", err);
setError("Gagal inisialisasi kontrak. Pastikan alamat dan ABI benar.");
}
}
}, [provider, account]);
// ... fungsi untuk interaksi kontrak akan ditambahkan di sini ...
return (
<div>
<h2>Interaksi Smart Contract</h2>
{contract ? (
<>
<p>Kontrak diinisialisasi di {CONTRACT_ADDRESS}</p>
{/* UI untuk memanggil fungsi kontrak */}
</>
) : (
<p>⚠️ Harap hubungkan dompet terlebih dahulu dan pastikan kontrak tersedia.</p>
)}
{error && <p style={{ color: 'red' }}>⚠️ {error}</p>}
</div>
);
}
export default ContractInteraction;
Penting: Pastikan MyContractABI.abi hanya berisi array ABI, bukan seluruh objek JSON. Beberapa alat kompilasi menghasilkan objek yang memiliki properti abi di dalamnya.
4. Membaca Data dari Smart Contract (View Functions)
Membaca data dari smart contract adalah interaksi yang paling sederhana. Fungsi-fungsi di Solidity yang dideklarasikan sebagai view atau pure tidak mengubah state blockchain dan tidak memerlukan gas fee. Ini berarti Anda bisa memanggilnya hanya dengan menggunakan provider (tanpa signer).
🎯 Contoh kasus: Anda ingin menampilkan total supply token ERC-20, nama token, atau nilai variabel publik di smart contract Anda.
Misalkan smart contract Anda memiliki fungsi getName() yang mengembalikan string.
// Lanjutan dari komponen ContractInteraction
// ... (state dan useEffect sebelumnya) ...
const fetchContractName = async () => {
setLoading(true);
setError('');
try {
if (!contract) {
setError("Kontrak belum diinisialisasi.");
return;
}
// Memanggil fungsi 'getName' dari smart contract
// Karena ini fungsi 'view', kita tidak perlu signer
const name = await contract.getName();
setDataFromContract(name);
console.log("Nama kontrak:", name);
} catch (err) {
console.error("Gagal mengambil nama kontrak:", err);
setError("Gagal mengambil nama kontrak.");
} finally {
setLoading(false);
}
};
return (
<div>
<h2>Interaksi Smart Contract</h2>
{contract ? (
<>
<p>Kontrak diinisialisasi di {CONTRACT_ADDRESS}</p>
<button onClick={fetchContractName} disabled={loading}>
{loading ? 'Mengambil...' : 'Ambil Nama Kontrak'}
</button>
{dataFromContract && <p>Nama: <strong>{dataFromContract}</strong></p>}
</>
) : (
<p>⚠️ Harap hubungkan dompet terlebih dahulu dan pastikan kontrak tersedia.</p>
)}
{error && <p style={{ color: 'red' }}>⚠️ {error}</p>}
</div>
);
✅ Tips: Selalu tangani state loading dan error untuk memberikan feedback yang baik kepada pengguna.
5. Mengirim Transaksi ke Smart Contract (State-Changing Functions)
Ini adalah bagian paling menarik: mengubah state blockchain! Fungsi-fungsi yang mengubah state (misalnya, mint(), transfer(), setGreeting()) memerlukan signer untuk menandatangani transaksi dan akan memakan gas fee. Saat Anda memanggil fungsi ini, dompet pengguna (seperti MetaMask) akan muncul untuk meminta konfirmasi transaksi.
🎯 Contoh kasus: Pengguna ingin mencetak NFT baru, mentransfer token, atau mengubah data di smart contract.
Misalkan smart contract Anda memiliki fungsi mint(address recipient, uint256 amount):
// Lanjutan dari komponen ContractInteraction
// ... (state, useEffect, fetchContractName sebelumnya) ...
const [mintAmount, setMintAmount] = useState('');
const [mintRecipient, setMintRecipient] = useState('');
const [transactionHash, setTransactionHash] = useState('');
const handleMintTokens = async () => {
setLoading(true);
setError('');
setTransactionHash('');
try {
if (!signer || !contract) {
setError("Dompet belum terhubung atau kontrak belum diinisialisasi.");
return;
}
// Pastikan mintAmount adalah angka yang valid dan ubah ke format BigNumber
const amount = ethers.parseUnits(mintAmount, 18); // Asumsi 18 desimal untuk token
// Buat instance kontrak dengan signer untuk mengirim transaksi
const contractWithSigner = contract.connect(signer);
// Memanggil fungsi 'mint' dari smart contract
const tx = await contractWithSigner.mint(mintRecipient, amount);
console.log("Transaksi Mint dikirim:", tx.hash);
setTransactionHash(tx.hash);
// Tunggu hingga transaksi dikonfirmasi
await tx.wait();
console.log("Transaksi Mint dikonfirmasi!");
alert("Mint berhasil!");
} catch (err) {
console.error("Gagal mengirim transaksi Mint:", err);
setError(`Gagal mengirim transaksi Mint: ${err.reason || err.message}`);
} finally {
setLoading(false);
}
};
return (
<div>
{/* ... (UI sebelumnya) ... */}
<h3>Mint Token Baru</h3>
<div>
<label>
Penerima:
<input
type="text"
value={mintRecipient}
onChange={(e) => setMintRecipient(e.target.value)}
placeholder="0x..."
/>
</label>
</div>
<div>
<label>
Jumlah:
<input
type="number"
value={mintAmount}
onChange={(e) => setMintAmount(e.target.value)}
placeholder="100"
/>
</label>
</div>
<button onClick={handleMintTokens} disabled={loading}>
{loading ? 'Mengirim Transaksi...' : 'Mint Token'}
</button>
{transactionHash && (
<p>Transaksi berhasil dikirim! Hash: <a href={`https://sepolia.etherscan.io/tx/${transactionHash}`} target="_blank" rel="noopener noreferrer">{transactionHash}</a></p>
)}
{error && <p style={{ color: 'red' }}>⚠️ {error}</p>}
</div>
);
}
⚠️ Peringatan:
- Selalu gunakan
contract.connect(signer)saat ingin mengirim transaksi. - Gunakan
ethers.parseUnits()atauethers.parseEther()untuk mengkonversi nilai yang dimasukkan pengguna (biasanya dalam bentuk desimal) ke formatBigNumberyang diterima smart contract. Kebalikannya, gunakanethers.formatUnits()atauethers.formatEther()untuk menampilkan nilai dari kontrak. - Sertakan link ke Etherscan (atau explorer yang relevan) agar pengguna bisa melacak transaksi mereka.
6. Mendengarkan Event dari Smart Contract
Smart contract bisa memancarkan event saat terjadi sesuatu yang penting. Ini sangat mirip dengan event di JavaScript (misalnya, click, change). Event ini disimpan di blockchain dan bisa didengarkan oleh aplikasi frontend untuk mendapatkan update real-time tanpa harus terus-menerus “meminta” (polling) data dari kontrak.
🎯 Contoh kasus: Anda ingin menampilkan notifikasi setiap kali ada token baru di-mint atau ditransfer.
Misalkan kontrak Anda memiliki event Transfer(address from, address to, uint256 amount).
// Lanjutan dari komponen ContractInteraction
// ... (state, useEffect, dan fungsi lainnya) ...
const [latestEvents, setLatestEvents] = useState([]);
useEffect(() => {
if (contract) {
// Mendengarkan event 'Transfer'
// Pastikan nama event sesuai dengan yang didefinisikan di Solidity
const transferFilter = contract.filters.Transfer(); // Bisa juga dengan parameter spesifik
const handleTransferEvent = (from, to, amount, event) => {
console.log(`Event Transfer: ${ethers.formatEther(amount)} dari ${from} ke ${to}`);
setLatestEvents((prevEvents) => [
{ from, to, amount: ethers.formatEther(amount), hash: event.log.transactionHash },
...prevEvents,
].slice(0, 5)); // Tampilkan 5 event terbaru
};
contract.on(transferFilter, handleTransferEvent);
console.log("Mulai mendengarkan event Transfer.");
// Cleanup function saat komponen di-unmount
return () => {
contract.off(transferFilter, handleTransferEvent);
console.log("Berhenti mendengarkan event Transfer.");
};
}
}, [contract]);
return (
<div>
{/* ... (UI sebelumnya) ... */}
<h3>Event Terbaru</h3>
{latestEvents.length === 0 ? (
<p>Belum ada event Transfer terbaru.</p>
) : (
<ul>
{latestEvents.map((event, index) => (
<li key={index}>
Dari: {event.from.substring(0, 6)}... Ke: {event.to.substring(0, 6)}... Jumlah: {event.amount} ETH.
(<a href={`https://sepolia.etherscan.io/tx/${event.hash}`} target="_blank" rel="noopener noreferrer">Tx</a>)
</li>
))}
</ul>
)}
</div>
);
}
Event adalah cara yang sangat efisien untuk membangun UI yang reaktif dan memberikan pengalaman real-time kepada pengguna DApp Anda.
Kesimpulan
Selamat! Anda kini telah memiliki pemahaman dasar dan contoh praktis tentang bagaimana menghubungkan frontend web Anda ke smart contract Ethereum menggunakan Ethers.js dan React. Kita sudah membahas cara menginisialisasi koneksi dompet, membaca data dari kontrak, mengirim transaksi yang mengubah state blockchain, hingga mendengarkan event untuk update real-time.
Membangun DApp memang sedikit berbeda dari aplikasi web tradisional, terutama karena adanya interaksi dengan dompet kripto dan konsep gas fee. Namun, dengan alat yang tepat seperti Ethers.js dan pemahaman tentang prinsip-prinsip dasarnya, Anda dapat menciptakan aplikasi yang powerful dan terdesentralisasi.
Teruslah bereksperimen, bangun proyek-proyek kecil, dan jelajahi lebih jauh ekosistem Web3. Potensinya sangat besar, dan keahlian Anda sebagai developer web akan sangat berharga di era baru internet ini!
🔗 Baca Juga
- Memulai Pengembangan DApps dengan Ethereum dan Solidity: Panduan Praktis untuk Developer Web
- Membuat Aplikasi Real-time dengan GraphQL Subscriptions: Panduan Lengkap
- Enkripsi Data Sensitif di Sisi Klien: Membangun Aplikasi Web yang Aman dengan Web Crypto API dan IndexedDB
- Membangun Backend Berperforma Tinggi dengan Konkurensi Go: Goroutine, Channel, dan Pola Praktis