Alpha Web3 & AI
Trang chủ › Phát Triển Web3

Xây dựng DApp đầu tiên của bạn với React và Ethers.js

Ban biên tập Alpha Web3 & AI · · Cập nhật
Xây dựng DApp đầu tiên của bạn với React và Ethers.js

Một DApp (ứng dụng phi tập trung) điển hình gồm smart contract chạy trên blockchain và giao diện web cho phép người dùng tương tác với hợp đồng qua ví. Với React làm giao diện và Ethers.js làm thư viện giao tiếp blockchain, bạn có thể xây DApp đầu tiên với bốn chức năng cốt lõi: kết nối ví, đọc dữ liệu từ hợp đồng, gửi giao dịch và cập nhật giao diện khi có sự kiện. Bài này hướng dẫn từng phần, dùng hợp đồng quỹ đóng góp đơn giản làm ví dụ.

Kiến trúc tổng quan của một DApp

  • Smart contract: logic và dữ liệu trên chuỗi. Bạn cần địa chỉ hợp đồng và ABI, file JSON mô tả các hàm và sự kiện.
  • Ví của người dùng: như MetaMask, giữ khóa riêng và ký giao dịch. Ví chèn một đối tượng provider vào trình duyệt theo chuẩn EIP-1193.
  • Frontend: ứng dụng React gọi Ethers.js để nói chuyện với ví và blockchain.
  • RPC node: nút blockchain để đọc dữ liệu, có thể qua ví hoặc qua nhà cung cấp RPC riêng.

Nếu chưa có hợp đồng, bạn có thể dùng hợp đồng FundMe trong bài thực hành lập trình smart contract, đã triển khai trên mạng thử nghiệm Sepolia.

Chuẩn bị dự án

  1. Tạo dự án React bằng Vite với mẫu React hoặc React + TypeScript.
  2. Cài thư viện ethers phiên bản 6.
  3. Sao chép ABI của hợp đồng từ thư mục artifacts của Hardhat (hoặc thư mục out của Foundry) vào thư mục src, ví dụ file fundme-abi.json.
  4. Lưu địa chỉ hợp đồng và ID mạng vào biến môi trường của Vite.

Ethers.js v6 có một số thay đổi so với v5, ví dụ dùng BrowserProvider thay cho Web3Provider, số lớn dùng kiểu BigInt gốc của JavaScript và các tiện ích như parseEther, formatEther được xuất trực tiếp. Khi đọc hướng dẫn cũ, hãy chú ý phiên bản.

Chức năng 1: Kết nối ví

Luồng kết nối gồm các bước:

  1. Kiểm tra trình duyệt có provider của ví không (đối tượng window.ethereum). Nếu không, hiển thị hướng dẫn cài ví thay vì báo lỗi khó hiểu.
  2. Khi người dùng bấm nút "Kết nối ví", tạo new BrowserProvider(window.ethereum) và gọi phương thức yêu cầu tài khoản eth_requestAccounts. Ví hiện hộp thoại xin phép.
  3. Lấy signer bằng provider.getSigner() và địa chỉ bằng signer.getAddress(), lưu vào state của React.
  4. Lắng nghe sự kiện accountsChanged và chainChanged từ ví để cập nhật giao diện khi người dùng đổi tài khoản hoặc mạng.

Nguyên tắc trải nghiệm: chỉ yêu cầu kết nối khi người dùng chủ động bấm, không bật hộp thoại ngay khi tải trang.

Kiểm tra đúng mạng

Gọi provider.getNetwork() để lấy chainId. Nếu không khớp mạng của hợp đồng, hiển thị nút "Chuyển mạng" dùng phương thức wallet_switchEthereumChain. Gửi giao dịch lên sai mạng là lỗi rất phổ biến.

Chức năng 2: Đọc dữ liệu từ hợp đồng

Đọc dữ liệu không cần ký và không tốn phí. Tạo một đối tượng hợp đồng chỉ đọc với new Contract(dia_chi, abi, provider), sau đó gọi các hàm view như gọi hàm JavaScript bất đồng bộ:

  • Lấy số dư hợp đồng bằng provider.getBalance(dia_chi_hop_dong), định dạng bằng formatEther để hiển thị đơn vị ETH.
  • Gọi contract.contributions(dia_chi_nguoi_dung) để lấy số đã đóng góp.
  • Gọi contract.owner() để biết người dùng hiện tại có phải chủ quỹ không, từ đó ẩn hoặc hiện nút rút tiền.

Trong React, đặt các lời gọi này trong một hook tùy chỉnh, gọi lại khi địa chỉ hoặc mạng thay đổi. Với ứng dụng lớn hơn, các thư viện như wagmi kết hợp TanStack Query giúp quản lý cache và trạng thái tải gọn gàng hơn.

Chức năng 3: Gửi giao dịch

Ghi dữ liệu cần signer. Tạo đối tượng hợp đồng gắn với signer, rồi gọi hàm thay đổi trạng thái:

  1. Chuyển số ETH người dùng nhập thành wei bằng parseEther. Kiểm tra đầu vào hợp lệ trước.
  2. Gọi contract.contribute({ value: so_wei }). Ví hiện hộp thoại xác nhận kèm phí gas ước tính.
  3. Lời gọi trả về đối tượng giao dịch ngay khi được gửi, kèm mã hash. Hiển thị trạng thái "Đang chờ xác nhận" và liên kết tới trình khám phá khối.
  4. Gọi tx.wait() để chờ giao dịch được đưa vào khối. Khi nhận biên nhận, cập nhật giao diện và đọc lại dữ liệu.

Xử lý lỗi thân thiện

Tình huốngCách hiển thị gợi ý
Người dùng từ chối trong víThông báo nhẹ nhàng, không coi là lỗi hệ thống
Hợp đồng revert với custom errorGiải mã lỗi từ ABI, hiển thị lý do dễ hiểu như "Số tiền dưới mức tối thiểu"
Không đủ ETH trả phíNhắc người dùng nạp thêm hoặc lấy từ faucet trên testnet
Sai mạngĐề nghị chuyển mạng
Giao dịch chờ lâuGiữ trạng thái chờ, cho phép xem trên trình khám phá khối

Ethers.js có tiện ích kiểm tra mã lỗi, ví dụ phân biệt lỗi người dùng từ chối (ACTION_REJECTED) với lỗi thực thi.

Chức năng 4: Lắng nghe sự kiện

Khi có người khác đóng góp, giao diện nên tự cập nhật. Dùng contract.on("Contributed", ham_xu_ly) để đăng ký lắng nghe sự kiện, và gỡ đăng ký khi component unmount để tránh rò rỉ bộ nhớ. Với lịch sử sự kiện, dùng contract.queryFilter trong một khoảng khối, nhưng lưu ý nhà cung cấp RPC thường giới hạn phạm vi truy vấn. Ứng dụng thực tế với nhiều dữ liệu lịch sử thường dùng dịch vụ lập chỉ mục như The Graph, như mô tả trong bài kiến trúc kỹ thuật Web3.

Lưu ý bảo mật cho frontend DApp

  • Không bao giờ đặt khóa riêng hay cụm từ khôi phục trong mã frontend; mọi thứ trong bundle đều công khai.
  • Không tin dữ liệu từ frontend: mọi kiểm tra quan trọng phải nằm trong smart contract.
  • Hiển thị rõ người dùng đang ký gì, số tiền bao nhiêu, gửi tới hợp đồng nào.
  • Tránh yêu cầu quyền chi tiêu token không giới hạn khi không cần thiết.
  • Khóa phiên bản thư viện và kiểm tra phụ thuộc; frontend DApp từng là mục tiêu của tấn công chuỗi cung ứng thư viện JavaScript.
  • Bảo vệ tên miền và DNS, vì chiếm quyền tên miền cho phép thay frontend bằng phiên bản độc hại.

Triển khai frontend

Build ứng dụng thành file tĩnh và triển khai lên dịch vụ hosting tĩnh, hoặc lên IPFS để tăng tính phi tập trung. Cấu hình RPC dự phòng để ứng dụng không phụ thuộc hoàn toàn vào ví người dùng khi chỉ cần đọc dữ liệu. Khi sẵn sàng ra mắt, cân nhắc triển khai hợp đồng trên một mạng Layer 2 để giảm phí cho người dùng, như phân tích trong bài giải pháp Layer 2.

Hướng phát triển tiếp

  • Hỗ trợ nhiều loại ví qua WalletConnect hoặc các thư viện như RainbowKit, ConnectKit.
  • Thêm đăng nhập bằng chữ ký theo chuẩn Sign-In with Ethereum nếu có backend.
  • Viết test giao diện với mạng cục bộ của Hardhat hoặc Anvil.
  • Thử thư viện viem, một lựa chọn hiện đại khác bên cạnh Ethers.js.

Câu hỏi thường gặp

Nên dùng Ethers.js hay Web3.js?

Cả hai đều dùng được. Ethers.js gọn nhẹ, tài liệu rõ ràng và phổ biến trong các dự án mới; Web3.js là thư viện lâu đời hơn. Ngoài ra viem kết hợp wagmi đang được nhiều đội React lựa chọn nhờ hỗ trợ TypeScript tốt.

Người dùng không cài MetaMask thì DApp có dùng được không?

Họ vẫn xem được dữ liệu nếu bạn cấu hình RPC riêng cho phần đọc. Để gửi giao dịch, họ cần một ví; bạn có thể hỗ trợ ví di động qua WalletConnect hoặc giải pháp ví nhúng tạo ví bằng email.

Vì sao giao dịch đã gửi nhưng giao diện chưa cập nhật?

Giao dịch cần thời gian để được đưa vào khối. Hãy chờ tx.wait() trả về biên nhận rồi mới đọc lại dữ liệu, và hiển thị trạng thái chờ để người dùng không gửi lặp lại.

Có thể dùng Next.js thay cho Vite không?

Có. Lưu ý rằng đối tượng ví chỉ tồn tại trên trình duyệt, nên mã truy cập window.ethereum phải chạy ở phía client, không chạy trong quá trình render phía máy chủ.

Bài liên quan