Interaksi Smart Contract dengan Web3.js dan Ethers.js pada jaringan blockchain

Pendahuluan: Dalam pengembangan dApp (aplikasi desentralisasi) di ekosistem Ethereum, Web3.js dan Ethers.js adalah dua library JavaScript paling populer untuk berinteraksi dengan smart contract dan blockchain Ethereum maupun jaringan EVM kompatibel lainnya[1]. Kedua library ini menyediakan antarmuka yang mempermudah tugas-tugas fundamental seperti mendepoloy smart contract, membuat dan mengelola wallet, menandatangani & mengirim transaksi, hingga membaca data/state blockchain tanpa harus memanggil JSON-RPC node secara manual[2]. Artikel tutorial ini akan membahas secara lengkap pengertian dan fungsi kedua library, perbandingan fitur dan arsitektur, cara instalasi dan setup, contoh koneksi ke jaringan Ethereum dan Polygon, contoh penggunaan untuk membaca dan menulis data ke smart contract (fungsi view maupun state-changing), penanganan gas fee dan event log, serta tips keamanan dan best practices. Diharapkan panduan ini mudah dipahami oleh pengembang pemula hingga menengah yang ingin mulai berinteraksi dengan smart contract menggunakan Web3.js maupun Ethers.js.
Pengertian dan Fungsi Web3.js dan Ethers.js
Web3.js adalah library JavaScript Ethereum original yang dikembangkan oleh Ethereum Foundation (kini dikelola oleh perusahaan ChainSafe) sejak tahun 2015[3]. Secara sederhana, Web3.js merupakan kumpulan modul yang memungkinkan aplikasi JavaScript berkomunikasi dengan node Ethereum (lokal maupun remote) melalui protokol RPC seperti HTTP, WebSocket, atau IPC[4]. Dengan Web3.js, developer dapat melakukan berbagai operasi blockchain Ethereum dari aplikasi JavaScript, misalnya membaca saldo akun, memanggil fungsi smart contract, mengirim transaksi, mengatur akun/wallet Ethereum, dan sebagainya[2]. Web3.js hingga kini sangat populer dan banyak digunakan di berbagai proyek Ethereum, serta memiliki komunitas dan dokumentasi yang luas mengingat usianya yang lebih tua.
Ethers.js adalah library JavaScript Ethereum yang lebih baru (dirilis pertama kali tahun 2015 oleh Richard Moore) sebagai alternatif yang lebih ringan dan modern dibanding Web3.js[5]. Menurut dokumentasi resminya, Ethers.js bertujuan menjadi library yang lengkap namun ringkas untuk berinteraksi dengan blockchain Ethereum dan ekosistemnya[6]. Ethers.js mencakup implementasi wallet Ethereum (termasuk manajemen kunci privat di sisi client), utilitas untuk format data (ABI, BigNumber, dll), serta abstraksi untuk penyedia jaringan (provider) dan smart contract[7][8]. Dengan Ethers.js, pengembang dapat dengan mudah terhubung ke jaringan Ethereum/EVM (melalui berbagai provider seperti JSON-RPC, Infura, Alchemy, Etherscan, dsb), membaca maupun mengubah state blockchain, serta menjaga kunci privat tetap aman di aplikasi (karena Ethers by design menyimpan kunci di sisi client)[7]. Ethers.js telah menjadi sangat populer dalam beberapa tahun terakhir dan banyak dipakai di proyek-proyek Web3 modern, sering dianggap lebih “bersih” dan terstruktur dalam hal pemisahan tanggung jawab dibanding Web3.js.
Singkatnya, kedua library ini memiliki fungsi utama yang sama: mempermudah developer JavaScript untuk berinteraksi dengan smart contract dan jaringan Ethereum/EVM. Web3.js dan Ethers.js memungkinkan aplikasi untuk memanggil fungsi-fungsi smart contract, mengirim transaksi on-chain, mengambil data on-chain (seperti saldo, state variabel, event log), dan tugas-tugas lain yang berkaitan dengan blockchain Ethereum. Perbedaan keduanya terletak pada detail implementasi, arsitektur, serta user experience yang akan dibahas di bagian selanjutnya.
Perbandingan Fitur dan Arsitektur Web3.js vs Ethers.js
Meskipun sama-sama powerful sebagai Ethereum JS API, Web3.js dan Ethers.js memiliki beberapa perbedaan penting dalam fitur dan arsitekturnya. Berikut adalah perbandingan kunci antara keduanya:
- Arsitektur Provider & Wallet: Salah satu perbedaan utama adalah bagaimana keduanya menangani provider (koneksi ke node) dan wallet (kunci privat untuk menandatangani transaksi). Web3.js secara tradisional menggabungkan kemampuan membaca dan menulis ke blockchain dalam satu objek Web3 yang menggunakan satu provider terpadu (misal local node atau Metamask) yang sekaligus dapat menyimpan akun untuk menandatangani transaksi. Sebaliknya, Ethers.js memisahkan peran tersebut menjadi dua entitas: Provider dan Signer/Wallet[9]. Provider di Ethers hanya untuk koneksi read-only ke blockchain (mengambil data blockchain, call view functions, dll), sedangkan Signer (Wallet) memegang kunci privat dan digunakan khusus saat mengirim transaksi yang memodifikasi state[7]. Pemisahan ini membuat desain Ethers.js lebih modular dan aman, karena kita bisa, misalnya, menggunakan provider publik (Infura/Alchemy) untuk membaca data dan hanya menggunakan wallet lokal (atau Metamask) saat benar-benar butuh menandatangani transaksi[7]. Web3.js tidak secara eksplisit memisahkannya – jika web3 terhubung ke Metamask atau node lokal dengan akun unlocked, objek yang sama digunakan untuk kedua keperluan.
- Ukuran Library dan Kinerja: Ethers.js dikenal lebih ringan dan performant dibanding Web3.js. Ukuran bundle Ethers ~ hanya 77 KB (terkompresi), sedangkan Web3.js berukuran beberapa megabyte[10]. Dampaknya, aplikasi front-end yang memuat Ethers.js akan lebih cepat dan ringan di browser dibanding memuat Web3.js yang jauh lebih besar. Ini menjadi pertimbangan penting terutama untuk aplikasi web (frontend DApp) agar waktu muat dan performa UI lebih baik.
- Popularitas & Dukungan: Web3.js, sebagai library yang lebih tua, sampai sekarang masih merupakan library Ethereum paling populer secara jumlah penggunaan. Banyak proyek legacy dan tutorial awal (seperti CryptoZombies) menggunakan Web3.js, sehingga mudah bagi pemula menemukan contoh dan Q&A terkait Web3[11][12]. Ethers.js meskipun muncul belakangan, kini popularitasnya tumbuh pesat dan bahkan telah melampaui Web3.js dalam hal npm downloads mingguan (sekitar 1 juta vs 600 ribu)[13]. Komunitas Ethers juga berkembang, namun sumber belajar mungkin sedikit lebih baru dibanding Web3. Keunggulan Web3 dalam hal ini adalah banyaknya kontribusi dan dukungan langsung Ethereum Foundation/Chainsafe, sementara Ethers dikembangkan lebih independen oleh satu orang (RicMoo) beserta kontributor komunitas[14].
- Kemudahan Penggunaan & API: Penilaian kemudahan bisa subjektif. Secara umum, banyak developer merasa API Ethers.js lebih bersih dan intuitif karena penggunaan promise/async-await penuh dan pemisahan concern yang jelas, sehingga kode untuk operasi blockchain bisa lebih ringkas. Contohnya, memanggil fungsi smart contract di Ethers bisa langsung dengan contract.myFunction() (untuk fungsi view akan otomatis di-call, untuk fungsi transaksional jika pakai signer akan otomatis mengirim tx), sedangkan di Web3 harus memilih contract.methods.myFunction().call() vs .send() tergantung jenis fungsinya. Namun di sisi lain, pemula mungkin merasa Web3.js sedikit lebih “straightforward” pada awalnya karena banyaknya contoh yang tersedia dan API yang terdokumentasi di berbagai sumber[15][16]. Secara historis, Web3 v0.x menggunakan callback, tapi versi 1.x sudah mendukung promise/events, mendekati kemudahan async-await seperti Ethers. Perlu dicatat juga, Ethers.js memiliki dokumentasi yang sangat komprehensif dan ramah bagi pemula (termasuk Getting Started guide dan playground interaktif)[17], sehingga mempelajarinya bisa terbantu dengan baik. Keduanya juga sudah mendukung TypeScript (Ethers sejak awal ditulis dalam TS, Web3.js versi terbaru juga menyediakan tipe TS karena diimplementasi ulang oleh ChainSafe) untuk memastikan pengalaman ngoding yang lebih aman.
- Fitur Khusus: Kedua library mencakup fitur-fitur standar Ethereum seperti koneksi ke berbagai jaringan, pembuatan akun/wallet, signing transaksi, deploy & interaksi kontrak, membaca event, utilitas encoding ABI, dan sebagainya[18]. Namun Ethers.js memiliki beberapa fitur tambahan yang nice-to-have, misalnya resolusi nama ENS bawaan (Anda bisa langsung menggunakan nama .eth di tempat address Ethereum)[19], dukungan format ABI human-readable, serta koleksi unit testing yang sangat luas (10rb+ test kasus) menjadikannya sangat andal[20]. Web3.js juga terus berkembang mengejar fitur serupa (misal kini sudah ada modul ENS di web3.eth.ens), namun Ethers cenderung lebih cepat mengadopsi fitur-fitur baru seperti EIP-1559 transaction types, dsb. Dari sisi lisensi open-source, Ethers.js menggunakan lisensi MIT yang lebih permisif, sedangkan Web3.js menggunakan LGPL-3.0; perbedaan ini umumnya tidak masalah bagi kebanyakan pengguna, kecuali jika Anda berniat memodifikasi library (MIT lebih bebas, LGPL mengharuskan publikasi source modifikasi)[21].
Intinya, tidak ada jawaban mutlak mana yang “lebih baik” – pilihan tergantung preferensi dan kebutuhan proyek. Jika Anda memulai proyek baru dan menginginkan library ringan, modular, dan modern, Ethers.js sering direkomendasikan. Namun jika Anda bekerja dengan codebase lama atau ingin memanfaatkan segudang tutorial/dukungan komunitas yang sudah ada, Web3.js juga tetap relevan. Banyak developer bahkan familiar dengan keduanya, karena konsep dasarnya serupa. Selanjutnya, mari kita masuk ke praktik penggunaan kedua library ini.
Cara Instalasi dan Setup Dasar Proyek
Sebelum mulai coding, pastikan Node.js sudah terpasang di komputer Anda (opsional jika Anda hanya ingin pakai di browser, tapi Node memudahkan pengembangan). Langkah awal untuk menggunakan Web3.js dan Ethers.js dalam proyek adalah menambahkannya sebagai dependency. Kedua library tersedia di npm, sehingga dapat diinstal via npm atau yarn:
npm install web3 ethers
Perintah di atas akan mengunduh paket web3 dan ethers terbaru ke proyek Anda (Anda dapat menambahkan –save jika masih menggunakan npm lama)[22][23]. Setelah instalasi, Anda bisa meng-import atau require library tersebut dalam kode JavaScript Anda:
// Contoh import di Node.js menggunakan CommonJS
const Web3 = require('web3');
const { ethers } = require('ethers');
// (Alternatif ES6 modules/TypeScript:
// import Web3 from 'web3';
// import { ethers } from 'ethers'; )
Pada kode di atas, kita mengimpor kedua library. Objek Web3 adalah constructor utama untuk membuat instance koneksi Ethereum dengan Web3.js, sedangkan Ethers diekspor dalam bentuk objek ethers yang berisi kelas-kelas dan fungsi utilitas (kita akan gunakan ethers.providers, ethers.Contract, dll melalui objek ini).
Di Browser: Jika Anda ingin menggunakan library ini langsung di aplikasi web (frontend) tanpa bundler, tersedia juga berkas UMD yang bisa di-include via tag <script>. Misalnya, Web3 menyediakan dist/web3.min.js yang bisa di-link langsung[22], dan Ethers juga menyediakan CDN (misal https://cdn.ethers.io/lib/ethers-5.2.umd.min.js)[24]. Namun, untuk keamanan disarankan meng-host sendiri file tersebut atau menggunakan bundler modern. Pada aplikasi web dengan MetaMask, Anda tidak perlu menginstal provider Ethereum secara manual — MetaMask akan menyuntikkan objek window.ethereum yang sesuai standar EIP-1193 sebagai provider. Anda tetap memerlukan library Web3/Ethers untuk memanfaatkan provider ini. Web3.js dapat menggunakan provider bawaan dengan Web3.givenProvider, sedangkan Ethers dapat membungkusnya dengan new ethers.providers.Web3Provider(window.ethereum)[25][26]. Selengkapnya tentang koneksi ke jaringan akan kita bahas di bawah.
Setelah library terpasang, setup dasar meliputi pembuatan instance dan mengatur provider (koneksi RPC ke node blockchain). Contohnya, untuk Web3.js Anda perlu membuat objek web3 dengan memberikan URL RPC atau provider. Untuk Ethers.js, Anda perlu membuat objek Provider. Bagian berikutnya akan menunjukkan implementasi konkret menghubungkan ke jaringan Ethereum dan Polygon.
Contoh Menghubungkan ke Jaringan Ethereum dan Polygon
Untuk berinteraksi dengan blockchain (Ethereum, Polygon, dll.), kita perlu terhubung ke node pada jaringan tersebut. Koneksi ini dilakukan melalui sebuah provider RPC. Anda bisa menjalankan node sendiri (misal geth, OpenEthereum, Nethermind, atau Polygon node) atau lebih umum menggunakan layanan node publik/berbayar seperti Infura, Alchemy, QuickNode, dsb. Berikut adalah contoh menghubungkan aplikasi menggunakan Web3.js dan Ethers.js ke jaringan Ethereum mainnet dan Polygon:
// --- Menggunakan Web3.js ---
const Web3 = require('web3');
// Koneksi ke Ethereum mainnet via Infura HTTP RPC:
const infuraUrl = "https://mainnet.infura.io/v3/<INFURA_PROJECT_ID>";
const web3Eth = new Web3(infuraUrl);
// Koneksi ke Polygon mainnet via RPC publik:
const polygonUrl = "https://polygon-rpc.com";
const web3Polygon = new Web3(polygonUrl);
// --- Menggunakan Ethers.js ---
const { ethers } = require('ethers');
// Provider Ethereum mainnet (Infura):
const providerEth = new ethers.providers.JsonRpcProvider(infuraUrl);
// Provider Polygon mainnet (Polygon RPC):
const providerPolygon = new ethers.providers.JsonRpcProvider(polygonUrl);
Pada kode di atas, untuk Ethereum kita menggunakan URL endpoint Infura (ganti <INFURA_PROJECT_ID> dengan Project ID Anda di Infura; Anda bisa mendapatkannya gratis dari dashboard Infura). Untuk Polygon, kita memakai RPC publik polygon-rpc.com (disediakan oleh Polygon, tanpa API key). Anda dapat mengganti URL tersebut sesuai kebutuhan, misalnya URL node lokal: “http://localhost:8545” (jika Anda menjalankan ganache atau node pribadi di port 8545), atau endpoint testnet seperti Sepolia, Goerli, Mumbai, dll. Koneksi berhasil dibuat jika tidak ada error; Anda dapat mencoba melakukan operasi sederhana seperti mendapatkan nomor blok terbaru untuk memastikan:
const latestEthBlock = await providerEth.getBlockNumber();
console.log("Ethereum block terkini:", latestEthBlock);
const latestPolyBlock = await web3Polygon.eth.getBlockNumber();
console.log("Polygon block terkini:", latestPolyBlock);
(Catatan: pada Web3.js v1.x, pemanggilan async bisa dilakukan dengan callback atau promise. Contoh di atas menggunakan await dengan asumsi dalam konteks async.)
Alternatif lain, menggunakan MetaMask di Browser: Jika aplikasi Anda berjalan di browser dan pengguna memiliki MetaMask, Anda dapat memanfaatkan provider yang disediakan extension tersebut tanpa perlu menulis URL RPC manual. MetaMask menyuntikkan window.ethereum yang bisa digunakan oleh Web3.js maupun Ethers. Contoh dengan Ethers:
// Ethers.js - koneksi via provider MetaMask (browser)
const web3Provider = new ethers.providers.Web3Provider(window.ethereum);
// Meminta akses akun MetaMask
await web3Provider.send("eth_requestAccounts", []);
const signer = web3Provider.getSigner();
Kode di atas akan meminta MetaMask untuk terhubung dan mengizinkan akses akun. Setelah di-approve, signer akan mewakili akun MetaMask pengguna yang dapat menandatangani transaksi[26]. Dengan Web3.js, Anda cukup mengecek Web3.givenProvider; MetaMask biasanya mengisinya secara otomatis jika terdeteksi[25]. Contoh: const web3 = new Web3(Web3.givenProvider || “http://localhost:8545”);. Jika givenProvider tidak null, berarti MetaMask tersedia dan terhubung (menggunakan jaringan sesuai yang dipilih user di MetaMask).
Koneksi ke jaringan Polygon via MetaMask juga serupa – pengguna cukup men-switch jaringan di MetaMask ke Polygon, dan provider EIP-1193 yang sama akan terhubung ke Polygon. Anda juga bisa secara manual menyiapkan jaringan Polygon di MetaMask (dengan memasukkan RPC URL Polygon dan Chain ID 137). Intinya, Web3.js dan Ethers.js dapat terhubung ke jaringan mana pun yang EVM compatible selama Anda memiliki URL RPC atau provider yang sesuai. Selanjutnya, setelah terkoneksi, kita bisa berinteraksi dengan smart contract di jaringan tersebut.
Contoh Membaca Data dari Smart Contract (Fungsi view/pure)
Salah satu operasi umum adalah membaca state atau output dari fungsi smart contract yang bertipe view atau pure (yaitu fungsi yang tidak mengubah state, hanya membaca/mengembalikan data). Fungsi-fungsi ini dapat dipanggil off-chain tanpa biaya gas. Mari kita lihat contoh cara membaca data smart contract dengan Web3.js dan Ethers.js.
Skenario: Misalkan kita memiliki sebuah smart contract sederhana dengan fungsi getValue() yang bersifat view dan mengembalikan sebuah nilai (misal uint256). Kita akan memanggil fungsi ini dari aplikasi JavaScript.
1) Menggunakan Web3.js: Untuk memanggil fungsi kontrak, kita perlu ABI (JavaScript Interface) dan alamat kontrak yang dituju. Dengan Web3.js, kita buat instance kontraknya melalui web3.eth.Contract. Lalu, untuk fungsi view, kita gunakan metode .call().
// Inisialisasi kontrak di Web3.js
const abi = [ /* ... ABI kontrak ... */ ];
const address = "0x1234567890abcdef..."; // alamat kontrak di blockchain
const contract = new web3Eth.eth.Contract(abi, address);
// Memanggil fungsi view (contoh: getValue)
const result = await contract.methods.getValue().call();
console.log("Nilai yang dibaca dari kontrak:", result);
Penjelasan: Objek contract mewakili smart contract yang sudah kita hubungkan (dengan ABI dan alamat). Kemudian contract.methods.getValue() mengacu pada fungsi tersebut. Karena ini fungsi view, kita panggil .call() untuk mengeksekusi secara read-only. Metode call() akan mengembalikan hasilnya tanpa mengirim transaksi on-chain (sehingga tidak butuh gas dan tidak mengubah state)[27]. Hasil result biasanya berupa tipe JavaScript yang sesuai dengan tipe return di Solidity. Perlu diperhatikan, jika tipe kembalian berupa angka besar (uint256), Web3.js mungkin mengembalikannya sebagai string atau objek bignumber (BN.js) tergantung konfigurasinya. Anda bisa mengonversi atau memformat sesuai kebutuhan (misal jika itu saldo wei, konversi ke ether menggunakan utilitas).
2) Menggunakan Ethers.js: Dengan Ethers, membaca data kontrak juga cukup sederhana. Kita buat instance kontrak menggunakan ethers.Contract. Berikan alamat kontrak, ABI, serta provider (karena untuk view only, provider saja cukup, tidak perlu signer). Lalu panggil fungsi kontrak tersebut seperti memanggil method pada objek JavaScript.
// Inisialisasi kontrak di Ethers.js
const contractEthers = new ethers.Contract(address, abi, providerEth);
// Memanggil fungsi view (misal: getValue)
const resultValue = await contractEthers.getValue();
console.log("Nilai dari kontrak (Ethers):", resultValue.toString());
Di sini contractEthers.getValue() akan otomatis melakukan call ke node Ethereum melalui provider, karena Ethers mengetahui fungsi getValue adalah constant/view (ABI menandakan itu view sehingga tidak perlu transaksi). Kita tidak perlu menambahkan .call() secara eksplisit – cukup memanggilnya layaknya fungsi async biasa. Jika kontrak membutuhkan parameter, tinggal memasukkan parameter dalam pemanggilan fungsi (misal contract.someFunction(param1, param2)). Hasil resultValue mungkin berupa objek tipe Ethers (contoh BigNumber untuk angka besar). Pada contoh di atas kita gunakan toString() sekadar untuk memastikan tampilannya terbaca (ini opsional, tergantung tipe datanya).
Perbedaan penting: Web3.js mengharuskan penggunaan .call() untuk fungsi view/pure dan .send() untuk fungsi yang mengubah state[27]. Ethers.js menyatukan pemanggilan fungsi – artinya pemanggilan contract.funcName() akan otomatis memilih mode call jika fungsi tidak mengubah state (constant), atau membuat transaksi jika fungsi mengubah state, tergantung apakah kontrak diinstansiasi dengan signer[28]. Jadi untuk membaca data, cukup pastikan kontrak instance-nya terhubung dengan provider (bukan signer) agar ia tidak mencoba mengirim transaksi. Ethers akan menangani pemanggilan view secara internal sebagai call yang tidak berbiaya.
Mendapatkan data kompleks: Kedua library bisa menangani output yang kompleks sesuai ABI. Misal, jika fungsi mengembalikan struktur atau array, Web3 akan mengembalikan object (dengan properti returnValues atau langsung array), sedangkan Ethers akan mengembalikan array (dengan properti juga) atau objek yang di-decorate. Anda bisa melakukan destructuring atau akses properti untuk mendapatkan nilai yang diinginkan. Referensi detail bisa dilihat di dokumentasi masing-masing.
Contoh Menulis Data / Mengirim Transaksi ke Smart Contract (Fungsi State-Changing)
Selain membaca, tentunya kita perlu bisa mengirim transaksi untuk memanggil fungsi smart contract yang mengubah state (misal fungsi tanpa modifier view/pure, yang biasanya menulis data atau melakukan aksi seperti transfer token). Pemanggilan seperti ini memerlukan biaya gas dan harus ditandatangani dengan kunci privat dari akun kita. Berikut contoh mengirim transaksi untuk memanggil fungsi state-changing dengan Web3.js dan Ethers.js.
Misal smart contract kita memiliki fungsi setValue(uint256 newValue) yang menyimpan nilai ke storage (mengubah state). Kita ingin memanggil setValue(42).
1) Menggunakan Web3.js: Untuk mengirim transaksi, kita akan menggunakan metode .send() pada objek metode kontrak. Namun sebelum itu, kita perlu akun yang akan digunakan untuk mengirim transaksi. Pada Web3.js, jika kita terhubung ke node Ethereum yang memiliki akun unlocked (contoh: ganache-cli default accounts, atau Geth dengan akun unlocked), kita bisa langsung menyebutkan address-nya sebagai from. Tapi jika menggunakan provider publik (Infura, dll) yang tidak mengelola kunci, maka kita harus menandatangani transaksi sendiri. Web3.js menyediakan modul web3.eth.accounts untuk meng-import kunci privat dan menandatangani transaksi.
Contoh: menambahkan akun dari private key dan mengirim transaksi:
// Tambahkan akun menggunakan private key (hati-hati, jangan expose kunci privat sembarangan!)
const account = web3Eth.eth.accounts.privateKeyToAccount('0xYOUR_PRIVATE_KEY');
web3Eth.eth.accounts.wallet.add(account);
// Panggil fungsi state-changing dengan .send()
const receipt = await contract.methods.setValue(42).send({
from: account.address,
gas: 100000 // gas limit
// gasPrice: optional, akan dibahas di bagian gas
});
console.log("Transaksi dikirim. Hash:", receipt.transactionHash);
Pada kode di atas, privateKeyToAccount akan membuat objek akun dari private key (harus diawali “0x”). Lalu kita menambahkannya ke wallet internal Web3 agar Web3 tahu akun tersebut bisa dipakai untuk menandatangani. Kemudian kita memanggil contract.methods.setValue(42).send(…) dengan menyertakan opsi from: account.address. Web3.js akan otomatis menandatangani transaksi tersebut menggunakan kunci si account yang sudah kita tambahkan (atau jika from kebetulan adalah akun unlocked di node, node akan menandatangani). Kita juga menyertakan gas limit (100000 sebagai contoh, angka ini tergantung kompleksitas fungsi – bisa diestimasi, lihat bagian gas fee). Jika diperlukan, kita bisa menyertakan gasPrice (dalam wei) atau maxFeePerGas & maxPriorityFeePerGas untuk transaksi EIP-1559. Apabila tidak disertakan, Web3.js biasanya akan mencoba menggunakan nilai gasPrice default dari jaringan[29]. Pemanggilan .send() akan mengembalikan Promise yang resolve ke receipt transaksi setelah transaksi terkirim dan masuk ke blockchain (by default, Web3 menunggu konfirmasi sejumlah blok tertentu sebelum menganggap transaksi selesai; ini bisa diatur dengan confirmationBlocks).
Jika Anda menggunakan MetaMask (browser), pemanggilan .send({from: userAddress}) akan memicu popup MetaMask meminta konfirmasi transaksi pada user. Pastikan from yang Anda set adalah salah satu akun MetaMask yang terhubung. MetaMask akan menandatangani transaksi dan mengirimkannya. Dalam kasus MetaMask, Anda tidak perlu memanggil privateKeyToAccount karena kunci privat disimpan oleh MetaMask, cukup panggil .send({from: ethereum.selectedAddress}) misalnya, dan MetaMask akan handle sisanya.
2) Menggunakan Ethers.js: Pada Ethers, konsepnya sedikit berbeda karena ada pemisahan provider dan signer. Untuk mengirim transaksi, kita membutuhkan Signer. Signer bisa berupa Wallet (jika kita punya private key) atau provider signer seperti MetaMask signer. Mari kita contohkan menggunakan kunci privat (untuk Node.js environment):
// Buat objek Wallet dari private key, terhubung ke provider
const wallet = new ethers.Wallet('0xYOUR_PRIVATE_KEY', providerEth);
// Buat instance kontrak dengan signer (wallet)
const contractWithSigner = new ethers.Contract(address, abi, wallet);
// Panggil fungsi state-changing
const txResponse = await contractWithSigner.setValue(42);
console.log("Transaksi dikirim. Hash:", txResponse.hash);
// Opsional: tunggu konfirmasi transaksi
const txReceipt = await txResponse.wait();
console.log("Transaksi dikonfirmasi pada blok", txReceipt.blockNumber);
Penjelasan: Pertama kita membuat Wallet instance menggunakan private key dan menghubungkannya ke providerEth (Ethereum mainnet dalam contoh ini). Ini menciptakan signer yang tahu cara menandatangani transaksi dan mengirimkannya ke jaringan melalui provider. Lalu kita buat ulang objek kontrak, tapi kali ini dengan signer. Kita bisa langsung new Contract dengan wallet, atau sebenarnya bisa juga melakukan contractEthers.connect(wallet) dari instance yang sebelumnya (hasilnya sama)[30]. Setelah kontrak memiliki signer, memanggil contractWithSigner.setValue(42) akan menyusun dan mengirim transaksi ke jaringan. Ethers mengemas detail penandatanganan secara internal. Variabel txResponse adalah objek transaksi yang berisi setidaknya hash transaksi (txResponse.hash)[31]. Kita kemudian dapat menunggu sampai transaksi mined dengan txResponse.wait(), yang mengembalikan objek receipt ketika berhasil masuk blok.
Apabila menggunakan MetaMask di front-end dengan Ethers, caranya: gunakan ethers.providers.Web3Provider(window.ethereum) seperti contoh sebelumnya, dapatkan signer = provider.getSigner(), lalu buat kontrak dengan signer tersebut: const contract = new ethers.Contract(address, abi, signer). Setelah itu, contract.setValue(42) akan memunculkan konfirmasi MetaMask secara otomatis, mirip seperti Web3.js versi browser.
Beberapa hal yang perlu diperhatikan saat mengirim transaksi: – Nonce dan Non-Blocking: Web3.js .send() dan Ethers .sendTransaction()/contract.method() sifatnya asynchronous. Pastikan Anda menunggu promise selesai atau menggunakan callback/event. Mengirim transaksi juga melibatkan nonce akun; library akan mengatur nonce secara otomatis (nonce adalah nomor urut transaksi dari akun tersebut). – Error Handling: Jika transaksi gagal (karena revert di kontrak, atau gas tidak cukup, dsb), library akan melempar error. Bungkus call dalam try/catch untuk menangkap kegagalan. Pada Web3, Anda bisa listen .on(‘error’, …) pada objek promiEvent .send(). – Transaction Receipt: Pada Web3, receipt bisa didapat dari hasil .send() (seperti contoh di atas) atau via event .on(‘receipt’, …). Pada Ethers, Anda harus memanggil tx.wait() untuk mendapatkan receipt. Receipt berisi informasi penting seperti status (sukses=1 atau gagal=0), gasUsed, logs event, dsb. – Keamanan Kunci Privat: Saat menggunakan private key langsung di code (seperti contoh di atas), jaga kerahasiaannya. Jangan pernah commit hardcode private key ke repository publik. Idealnya, gunakan variabel lingkungan (.env) untuk menyimpan key, atau pertimbangkan penggunaan dompet hardware untuk operasi di mainnet yang besar. (Lihat juga bagian tips keamanan di bawah.)
Penanganan Gas Fee dan Event Logs
Gas Fee dan Pengaturan Gas
Setiap transaksi yang mengubah state di Ethereum (atau chain EVM lain) memerlukan gas fee. Gas di Ethereum adalah satuan kerja komputasi, dan fee dihitung berdasarkan gas yang digunakan dikalikan dengan harga gas (gas price) yang kita tentukan[32]. Sederhananya, gas fee adalah biaya (dalam ETH atau token native jaringan) yang kita bayarkan kepada miner/validator agar transaksi kita diproses. Berikut beberapa poin penting terkait gas: – Gas Limit: Setiap transaksi memiliki gas limit, yaitu jumlah gas maksimum yang kita izinkan untuk digunakan[33]. Misal kita menetapkan gas limit 100000, jika eksekusi kontrak hanya butuh 50k gas, sisanya akan dikembalikan; jika butuh lebih dari 100k gas, transaksi akan out of gas dan gagal (tetap membayar gas yang terpakai). – Gas Price: Adalah besaran harga per unit gas (dalam Wei). Di Ethereum sekarang (pasca EIP-1559), konsepnya sedikit berubah menjadi base fee (yang dibakar) dan priority fee (tip), namun untuk sederhana kita asumsikan gas price sebagai total yang bersedia kita bayarkan per gas[34]. Harga gas bervariasi tergantung kondisi jaringan (satuan biasanya Gwei, yaitu 1e-9 ETH). Semakin tinggi gas price, transaksi Anda akan di-prioritize lebih cepat oleh miner. – Biaya total: = gas used * gas price. Misal fungsi Anda mengonsumsi 50,000 gas dan gas price 20 Gwei, maka fee = 50,000 * 20 Gwei = 1,000,000 Gwei = 0.001 ETH. Pada jaringan seperti Polygon, biaya dihitung dengan satuan MATIC, biasanya lebih murah per gas-nya.
Baik Web3.js maupun Ethers.js biasanya dapat otomatis menentukan gas price dan gas limit jika Anda tidak menyetorkannya, namun sangat disarankan untuk mengerti cara mengaturnya sendiri: – Estimasi Gas Limit: Anda dapat meminta library mengestimasi kebutuhan gas untuk suatu panggilan transaksi sebelum benar-benar mengirim. Web3.js menyediakan contract.methods.myFunc(…).estimateGas({…}), dan Ethers.js menyediakan contract.estimateGas.myFunc(…). Contoh:
// Estimasi gas menggunakan Web3.js
const gasNeeded = await contract.methods.setValue(42).estimateGas({ from: account.address });
console.log("Perkiraan gas:", gasNeeded);
// Estimasi gas menggunakan Ethers.js
const gasEstimate = await contractWithSigner.estimateGas.setValue(42);
console.log("Perkiraan gas (ethers):", gasEstimate.toString());
Hasil estimasi ini bisa dipakai sebagai gas limit (sering kali ditambah sedikit buffer, misal +10-20% untuk amannya).
- Mengambil Gas Price terkini:js: const gasPrice = await web3Eth.eth.getGasPrice(), Ethers.js: const gasPrice = await providerEth.getGasPrice(). Ini memberi nilai gas price rata-rata jaringan saat ini (dalam wei)[35][36]. Anda bisa menggunakan nilai ini atau menyesuaikannya (lebih tinggi untuk percepat, lebih rendah untuk hemat biaya tapi mungkin pending lebih lama).
Khusus Ethers (pasca EIP-1559), getGasPrice() sebenarnya memberikan gas price yang disarankan (yang ekuivalen dengan baseFee+tip). Anda juga bisa mendapatkan feeData = await provider.getFeeData() yang mengandung maxFeePerGas dan maxPriorityFeePerGas. Namun, jika tidak diatur manual, Ethers akan otomatis mengisi field tersebut dengan nilai yang wajar.
Saat mengirim transaksi dengan kedua library, Anda bisa menyertakan parameter gas ini. Contoh Web3: .send({from, gas: 100000, gasPrice: web3.utils.toWei(’20’,’gwei’)}). Contoh Ethers: contractWithSigner.setValue(42, { gasLimit: 100000, gasPrice: ethers.utils.parseUnits(’20’, ‘gwei’) }). Jika Anda tidak menyebutkan sama sekali, library akan mencoba mengestimasikan sendiri. Menentukan gas limit yang tepat penting – terlalu rendah menyebabkan transaksi gagal (dan gas terbuang), terlalu tinggi tidak membuang gas ekstra (sisanya dikembalikan) tapi jangan set berlebihan tanpa alasan.
Terakhir, ingat bahwa chain berbeda punya harga gas berbeda. Ethereum L1 misal bisa mahal di saat network sibuk, sedangkan Polygon jauh lebih murah per gas. Namun Polygon mungkin membutuhkan gas limit yang sedikit lebih tinggi untuk operasi serupa karena perbedaan opcodes cost, dsb. Selalu uji transaksi di jaringan ujicoba atau dengan jumlah kecil dulu jika ragu[37].
Event Logs dan Cara Mendengarkannya
Event pada smart contract Ethereum adalah mekanisme logging yang disimpan di blockchain saat transaksi sukses. Event sering digunakan untuk memberikan notifikasi atau data keluaran dari aksi on-chain (misal event Transfer pada token ERC-20 yang mencatat perpindahan token). Dari sisi aplikasi, kita dapat mendengarkan (subscribe) event secara real-time, atau membaca event log historis dari blockchain.
Mendengarkan Event secara real-time: – Dengan Web3.js: Web3 menyediakan beberapa cara, di antaranya contract.events.EventName() untuk berlangganan event tertentu, atau web3.eth.subscribe(‘logs’, options) untuk subscribe log secara general. Cara yang lebih mudah adalah pada instance contract kita. Contoh, jika smart contract memiliki event ValueChanged(address sender, uint newValue), kita bisa:
contract.events.ValueChanged({
filter: {}, // bisa menyaring event berdasarkan parameter, optional
fromBlock: 'latest' // mulai subscribe dari block terakhir
})
.on('data', event => {
console.log("Event ValueChanged terdeteksi:", event.returnValues);
})
.on('error', console.error);
Ketika ada transaksi baru yang mengemit event ValueChanged, callback .on(‘data’) akan terpanggil dengan objek event yang memuat detail seperti event.returnValues (isi parameter event), event.blockNumber, event.transactionHash, dll. Untuk menggunakan subscription seperti ini disarankan menggunakan provider WebSocket (misal konek ke wss://mainnet.infura.io/ws/v3/…). Jika memakai HTTP provider, Web3 akan melakukan polling periodik untuk mengecek block baru (kurang real-time).
- Dengan Ethers.js: Ethers juga menyediakan kemampuan subscribe event melalui objek Contract maupun Provider. Anda bisa langsung menggunakan on(eventName, listener). Contoh:
contractWithSigner.on("ValueChanged", (sender, newValue, event) => {
console.log(`Event ValueChanged: ${sender} set value = ${newValue.toString()}`);
// 'event' memiliki info seperti event.blockNumber, event.transactionHash, dll.
});
Ketika event terjadi, fungsi listener kita dipanggil dengan argumen sesuai definisi event (diikuti objek event terakhir). Ethers akan men-setup filter event tersebut di provider. Perlu diketahui, jika menggunakan HTTP provider, Ethers secara default melakukan polling setiap x detik untuk cek event. Jika ingin yang push, gunakan WebSocketProvider. Anda juga bisa memanfaatkan provider.on(‘pending’, …) atau provider.on(‘block’, …) untuk kasus khusus, tapi umumnya contract.on sudah memadai.
Membaca Event Log historis (query): Selain subscribe untuk event baru, kita mungkin ingin mengambil semua event yang pernah terjadi (atau dalam rentang blok). Ini berguna misalnya untuk menampilkan riwayat transaksi. – Dengan Web3.js, gunakan contract.getPastEvents(eventName, { fromBlock: X, toBlock: Y, filter: {…} }). Ini akan meminta node untuk mencari log event tersebut dalam rentang blok yang ditentukan. Contoh: const past = await contract.getPastEvents(‘ValueChanged’, { fromBlock: 0, toBlock: ‘latest’ }); akan mengambil semua event sejak genesis. – Dengan Ethers.js, tersedia metode contract.queryFilter(filter, fromBlock, toBlock). Anda bisa mendapatkan objek filter via contract.filters.EventName(optionalParams…). Contoh: const filter = contract.filters.ValueChanged(null, null); const events = await contract.queryFilter(filter, 0, “latest”);. Hasilnya adalah array event serupa dengan event object realtime tadi.
Perlu diingat untuk penggunaan query ini, node publik seperti Infura biasanya memiliki limit jumlah blok yang bisa dipindai dalam satu request. Jadi untuk data besar, mungkin perlu dibagi per batch rentang blok.
Menangani Event dalam Transaksi: Saat kita mengirim transaksi yang menghasilkan event, kita bisa langsung mendapatkan event tersebut dari transaction receipt. Both Web3 and Ethers menyertakan logs di receipt. Web3.js misalnya, receipt.events akan berisi objek event dikelompokkan per nama event. Ethers, txReceipt.logs berisi raw log dan bisa diparse via ABI if needed, atau jika pakai contract.once() sebelum send juga bisa.
Dengan memanfaatkan event logs, aplikasi Anda dapat memberi umpan balik ke pengguna (misal “Transaksi sukses, event X diterima”), atau memicu tindakan lanjutan off-chain saat event tertentu muncul.
Tips Keamanan dan Best Practices
Mengembangkan aplikasi yang berinteraksi dengan blockchain memerlukan perhatian khusus pada keamanan, karena kesalahan bisa berakibat kehilangan aset atau celah bagi penyerang. Berikut beberapa best practices dan tips keamanan saat menggunakan Web3.js/Ethers.js dan membangun aplikasi Web3:
- Jaga Kerahasiaan Kunci Privat & Seed Phrase: Jangan pernah membagikan atau menyimpan kunci privat Anda dalam format plaintext di kode publik. Gunakan mekanisme aman: simpan di environment variable, atau lebih baik lagi gunakan Hardware Wallet untuk menyetujui transaksi penting. Pastikan frasa seed wallet disimpan offline (tulis di kertas dan simpan di tempat aman), bukan di komputer atau cloud storage[38]. Banyak malware yang dapat mencari file yang mengandung seed phrase atau key, jadi hati-hati dengan data wallet Anda.
- Gunakan Wallet/Provider Terpercaya: Saat berinteraksi di front-end, percayakan penanganan kunci kepada wallet yang reputable seperti MetaMask, Coinbase Wallet, Trust Wallet, dll. Pastikan wallet app tersebut selalu versi terupdate untuk mendapatkan patch keamanan terbaru[39][40]. Jangan pernah memasukkan seed phrase atau private key Anda ke website yang mencurigakan. Integrasikan wallet via mekanisme resmi (contoh: EIP-1193 provider untuk MetaMask seperti dalam contoh sebelumnya).
- Verifikasi Smart Contract dan Sumber Daya: Sebelum berinteraksi dengan sebuah smart contract (terutama kontrak publik yang bukan Anda deploy sendiri), pastikan kontrak tersebut terpercaya. Cek apakah kode sumbernya diverifikasi (misal di Etherscan/Polygonscan) dan apakah telah di-audit oleh auditor keamanan yang kredibel[41]. Berhati-hatilah terhadap kontrak scam/phishing. Sebagai pengembang, Anda juga sebaiknya hanya menghubungkan DApp Anda ke kontrak yang sudah diverifikasi dan benar. Jika memungkinkan, batasi akses hanya ke alamat kontrak yang diinginkan (jangan mudah-mudah memanggil fungsi alamat kontrak yang dikirim user tanpa validasi).
- Uji Coba di Jaringan Uji & Dengan Nilai Kecil: Selalu lakukan percobaan di testnet (Goerli, Sepolia, Mumbai, dll) sebelum di mainnet. Ini untuk memastikan kode interaksi Anda berjalan benar. Jika testnet tidak tersedia dan Anda harus mencoba di mainnet, mulailah dengan nilai yang kecil terlebih dahulu[37] untuk mengurangi risiko kerugian jika ada bug. Contohnya, coba transfer 0.001 ETH dulu sebelum 1 ETH, dsb.
- Tangani Error dan Edge Cases: Pastikan setiap pemanggilan .call() atau .send() Anda memiliki error handling. Misal, jika memanggil fungsi view untuk membaca mungkin bisa gagal karena node error atau contract revert (contoh: mencoba membaca data dari blok yang sudah finality, dsb). Begitu juga transaksi, tangkap exception agar tidak membuat aplikasi hang. Berikan informasi yang berguna ke pengguna jika transaksi gagal (contoh: out of gas, revert message). Ini meningkatkan keamanan dan UX, karena pengguna tahu apa yang terjadi.
- Batasi Penggunaan Gas secara Bijak: Jangan mengirim transaksi dengan gas price yang terlalu rendah (bisa nyangkut lama) atau gas limit terlalu pas-pasan. Sebaliknya, memberi gas limit terlalu tinggi tidak membahayakan secara langsung (excess gas dikembalikan), tapi jika terlalu tinggi bisa menutupi bug di mana fungsi sebenarnya looping tak terhingga (walau biasanya ada cap block gas limit). Gunakan estimateGas selalu, dan bisa tambahkan buffer ~10-15%. Selain itu, untuk mencegah kehabisan gas karena perubahan state dynamic (misal loop berdasarkan input user), pertimbangkan untuk membatasi input di sisi frontend.
- Waspadai Serangan Phishing & XSS: Jika DApp Anda berbentuk web, pastikan melakukan sanitasi input pengguna dan waspada terhadap injeksi script (XSS). Serangan XSS pada DApp bisa mencuri akses user (misal injeksi panggilan ethereum.request({method:’eth_sendTransaction’, params:[…]}) terselubung). Gunakan Content Security Policy yang ketat dan hindari memasukkan data tak terpercaya secara langsung ke DOM. Selain itu, selalu ingatkan user untuk memeriksa URL aplikasi Anda (untuk menghindari phishing site yang meniru DApp Anda)[42].
- Update Library ke Versi Terbaru: Secara berkala cek versi Web3.js/Ethers.js yang Anda pakai. Update ke versi terbaru jika memungkinkan, karena pembaruan sering kali mencakup perbaikan bug dan patch keamanan. Misal Ethers v6 (rilis terbaru) membawa banyak peningkatan. Hati-hati saat upgrade major version (baca migration guide), tapi usahakan tidak tertinggal jauh versinya.
- Penggunaan Best Practices Solidity di Off-chain: Maksudnya, pahami juga implikasi dari apa yang Anda panggil. Contoh: jika sebuah fungsi smart contract tidak pure (membaca state) tapi Anda memanggilnya dengan .call secara off-chain, maka hasil yang Anda lihat mungkin tidak tercermin di chain (karena Anda tidak benar-benar mengubah state). Ini bukan bug keamanan tapi bisa membingungkan. Jadi selalu bedakan mana operasi hanya baca dan mana yang harus transaksi. Gunakan .call() hanya untuk view/pure functions (seperti sudah dibahas)[27].
- Audit Kode Frontend/Backend: Meskipun smart contract sering jadi fokus audit, kode di sisi client atau server yang menggunakan Web3.js/Ethers juga penting. Pastikan Anda tidak memiliki log yang menyimpan informasi sensitif (contoh: jangan console.log private key), atau endpoin backend yang mengekspos kunci tanpa otorisasi. Implementasikan HTTPS dan secure context jika aplikasi web.
Dengan mengikuti praktik-praktik di atas, Anda dapat mengurangi risiko kesalahan maupun serangan. Dunia Web3 menawarkan kebebasan kendali penuh atas aset, tapi berarti tanggung jawab keamanan ada di tangan pengguna dan developer. Selalu berhati-hati dalam setiap langkah integrasi dengan blockchain.
Referensi Dokumentasi Resmi dan Sumber Pembelajaran Tambahan
- Dokumentasi Resmi Web3.js: Ethereum JavaScript API (web3js) – petunjuk instalasi, API reference, dan contoh penggunaan[22][4]. Tersedia di: web3js.readthedocs.io dan docs.web3js.org. Sumber ini mencakup semua modul Web3 (web3-eth, web3-utils, dll) dengan penjelasan mendalam.
- Dokumentasi Resmi Ethers.js: Ethers v5 Documentation – panduan lengkap untuk library Ethers, mulai Getting Started, konsep Provider/Signer, Interaksi Kontrak, sampai referensi API detail[6][28]. Lihat di: docs.ethers.io. Juga tersedia dokumen untuk Ethers v6 dengan update terbaru.
- Artikel Perbandingan Web3.js vs Ethers.js (Alchemy) – SDK Comparison: Web3.js vs Ethers.js oleh Alchemy. Membahas perbedaan, kelebihan, kekurangan, performa, popularitas, dll secara mendalam[3][43]. Sumber bagus untuk menentukan pilihan library sesuai kebutuhan proyek.
- QuickNode Guides: Tutorial praktis terkait Web3.js dan Ethers.js. Contohnya: How to Connect to Ethereum Network with Web3.js – panduan langkah demi langkah koneksi provider dan mengambil blok terbaru[44], How to Interact with Smart Contracts (Web3.js & Ethers.js) – contoh interaksi kontrak menggunakan kedua library[45]. Lihat koleksi di QuickNode (quicknode.com/guides).
- Sumber Belajar Tambahan:
- Ethereum Stack Exchange – tempat tanya jawab teknis seputar Ethereum development. Banyak solusi praktis untuk masalah penggunaan Web3.js/Ethers.
- CryptoZombies – walau fokus ke Solidity, tutorial game ini menggunakan Web3.js di bagian akhir untuk interact dengan kontrak, dapat membantu pemahaman konteks penuh dApp.
- Blog & YouTube DApp University, Patrick Collins, etc. – banyak konten video dan artikel yang menjelaskan penggunaan Web3.js/Ethers.js dalam proyek nyata.
- Metamask Docs (Developer) – panduan integrasi MetaMask dengan contoh Ethers.js vs Web3.js untuk hal-hal seperti membaca balance token ERC-20[46].








