You are on page 1of 65

IE102.

M21- Các công nghệ nền | Nhóm 4

ĐẠI HỌC QUỐC GIA THÀNH PHỐ HỒ CHÍ MINH


TRƯỜNG ĐẠI HỌC CÔNG NGHỆ THÔNG TIN
KHOA KHOA HỌC VÀ KỸ THUẬT THÔNG TIN

BÁO CÁO ĐỒ ÁN CUỐI KỲ

MÔN HỌC: CÁC CÔNG NGHỆ NỀN


Đề tài: XÂY DỰNG MARKETPLACE NFT

Lớp: IE102.M21 - Nhóm: 4


GVHD: ThS. Võ Tấn Khoa

Nhóm sinh viên thực hiện:

STT MSSV Tên


1 18520063 Bùi Quang Huy
2 18521682 Nguyễn Ngọc Thúy Vy
3 18521692 Nguyễn Phước Duy
4 19521403 Phan Đặng Tiến Dũng
5 19521558 Nguyễn Thị Huệ
6 19521733 Võ Thị Như Lài
7 19521746 Nguyễn Trúc Lân

□□Tp. Hồ Chí Minh, 05/2022 □□

1
IE102.M21- Các công nghệ nền | Nhóm 4

MỤC LỤC

MỤC LỤC 2
LỜI MỞ ĐẦU 4
CHƯƠNG 1: TỔNG QUAN VỀ ĐỀ TÀI 5
1.1 Lý do chọn đề tài 5
1.2 Yêu cầu của đề tài 5
1.3 Đối tượng 5
1.4 Phạm vi ứng dụng 5
1.5 Đặc điểm 5
1.6 Các module 6
1.7 Công cụ triển khai 6
CHƯƠNG 2: CƠ SỞ LÝ THUYẾT 7
2.1 Ngôn ngữ lập trình Solidity 7
2.2 Nền tảng Blockchain – Polygon (MATIC) 7
2.3 Tiêu chuẩn ERC-721 9
2.4 Frontend Frameworks 10
2.5 Ethereum Development Environment – Hardhat 11
2.6 Storage Platforms - IPFS 12
2.7 Ethereum Web Client Library - Ethers.js 12
CHƯƠNG 3: THIẾT KẾ HỆ THỐNG 13
3.1 Các thành phần 13
3.2 Các bước thực hiện 14
CHƯƠNG 4: CÀI ĐẶT MÔI TRƯỜNG 15
4.1 Tạo một ứng dụng Next.js 15
4.2 Cài đặt các dependencies cho ứng dụng 15
4.3 Cài đặt framework Tailwind 15
4.4 Cấu hình Hardhat 15

2
IE102.M21- Các công nghệ nền | Nhóm 4

CHƯƠNG 5: PHÂN TÍCH TRANG WEB 17


5.1 Smart contract 17
5.2 Màn hình trang chủ 21
5.3 Màn hình Sell NFT 27
5.4 Màn hình My NFTs 30
5.5 Màn hình Detail NFT 34
5.6 Màn hình Resell NFT 40
5.7 Màn hình Dashboard 42
5.8 Thanh Navbar (_app.js) 46
5.9 Login 48
5. 10 Thiết kế css 56
5. 11 API 59
CHƯƠNG 6: CÀI ĐẶT VÀ THỬ NGHIỆM 61
CHƯƠNG 6: NHẬN XÉT VÀ KẾT LUẬN 62
6.1 Kết quả đạt được 62
6.2 Ưu điểm 62
6.3 Nhược điểm 62
6.4 Hướng phát triển 62
TÀI LIỆU THAM KHẢO 63
PHÂN CHIA CÔNG VIỆC 64

3
IE102.M21- Các công nghệ nền | Nhóm 4

LỜI MỞ ĐẦU
NFT (Non-fungible token) là một tài sản số sử dụng công nghệ blockchain.
Làn sóng NFT phát triển mạnh mẽ trong thời gian gần đây và trở thành một trong
những nền tảng quan trọng của nền kinh tế tiền mã hóa. NFT đang được sử dụng
trong nhiều lĩnh vực như Nghệ thuật, Gaming, Metaverse, Collectible,… Cùng với
tiềm năng của NFT là nhu cầu tìm kiếm một nền tảng để có thể sở hữu, mua bán các
sản phẩm NFT với nhiều mục đích khác nhau được gọi là NFT Marketplace.
Trước nhu cầu đó, cùng với yêu cầu môn học, nhóm chúng em quyết định chọn đề
tài Xây dựng NFT Marketplace.
Với đề tài và môn học này, nhóm xin chân thành cảm ơn sự giúp đỡ tận tình
của Thầy Võ Tấn Khoa. Song, do còn nhiều hạn chế đề tài nhóm xây dựng không
tránh khỏi những thiếu sót. Rất mong được thầy cô và các bạn đóng góp ý kiến để
chương trình ngày càng được hoàn thiện.

Trân trọng cảm ơn!

4
IE102.M21- Các công nghệ nền | Nhóm 4

CHƯƠNG 1: TỔNG QUAN VỀ ĐỀ TÀI


1.1 Lý do chọn đề tài
NFT là tài sản mật mã được tạo trên công nghệ blockchain có mã nhận dạng duy
nhất và siêu dữ liệu, giúp chúng có thể phân biệt, khác biệt và hoàn toàn duy nhất.
NFT là tài sản kỹ thuật số đại diện cho các tài sản trong thế giới thực như nghệ
thuật, âm nhạc, vật phẩm trong trò chơi và video được liên kết với bằng chứng
quyền sở hữu. 
Vì vậy NFT Marketplace ra đời để có thể người dùng có thể tạo, mua hoặc bán
NFT. Thị trường này cũng được sử dụng để lưu trữ, hiển thị hoặc hiển thị giao dịch
và tạo mã thông báo NFT hoặc bất kỳ tài sản kỹ thuật số nào. Đối với người dùng,
trải nghiệm thị trường NFT thuận tiện như một sàn giao dịch tiền điện tử.
1.2 Yêu cầu của đề tài 
- Nắm được các kiến thức cơ bản về smart contract, cách deploy một smart
contract,...
-Tạo ra một sàn giao dịch NFT có các chức năng cơ bản như:cửa sổ mua sắm,
ví, đăng ký tài khoản, tìm kiếm,...
1.3 Đối tượng
1.3.1 Guest: 
- Khách viếng thăm
- Xem thông tin sản phẩm cũng như các tin tức khác
- Đăng ký để kết nối với ví Metamask
1.3.2 User:
- Đã có tài khoản kết nối với ví Metamask
- Đặt mua sản phẩm
- Bán sản phẩm
1.4 Phạm vi ứng dụng
Người dùng có nhu cầu tạo, thu thập, sưu tầm, mua và bán các NFT để thu
lợi nhuận.
1.5 Đặc điểm 
Website được thiết kế với
- Giao diện hài hoà, thân thiện, giúp người dùng dễ dàng sử dụng.
- Trang chủ sẽ hiển thị danh sách các sản phẩm được bán.
- Khách hàng có thể dễ dàng tìm thấy thông tin chi tiết các sản phẩm mà họ
quan tâm.
- Khách hàng có thể tìm kiếm sản phẩm.
- Có chức năng đăng ký và liên kết với ví Metamask

5
IE102.M21- Các công nghệ nền | Nhóm 4

1.6 Các module


1.6.1 Module sản phẩm
Các NFT được liệt kê trên thị trường phải hiển thị tất cả thông tin quan
trọng theo cách tối ưu hóa nhất: tên, giá, mô tả, chủ sở hữu, phương thức
thanh toán, v.v. 
1.6.2 Module đăng nhập
Đăng nhập bằng cách liên kết với ví Metamask hiện có của khách hàng
vào NFT Marketplace.
1.6.3 Module tìm kiếm sản phẩm
Các sản phẩm tại trang chủ có thể được tìm kiếm để hiển thị ra sản phẩm
phù hợp nhất với yêu cầu khách hàng.
1.6.4 Module quản lý sản phẩm
Người dùng có thể cập nhật những sản phẩm mình đã mua và đã bán
trong danh mục sản phẩm của mình.
1.7 Công cụ triển khai
- IDE: Visual Studio Code
- Quản lý code: Github
- Quản lý công việc: Excel
- Trao đổi, liên lạc: Microsoft Team, Facebook

6
IE102.M21- Các công nghệ nền | Nhóm 4

CHƯƠNG 2: CƠ SỞ LÝ THUYẾT 

Tech stack:
● Programing Language - Solidity
● Blockchain Platform - Polygon (MATIC)
● NFT Standards EERC-721
● Front-end Frameworks
● Web application framework - Next.js
● CSS framework - CSS Tailwind
● Storage Platforms  - IPFS
● Ethereum Web Client Library - Ethers.js

2.1 Ngôn ngữ lập trình Solidity 


Solidity được biết đến là một ngôn ngữ lập trình hướng contract, được sử dụng
hầu hết trong smart contract (nâng cấp và tạo lập) thuộc hệ sinh thái Ethereum.
Solidity có các cú pháp và câu lệnh gần giống với các ngôn ngữ lập trình phổ biến
hiện nay (Java, C++, C#...). Ngôn ngữ Solidity có những ứng dụng vô cùng quan
trọng đối với công nghệ Blockchain. Một ví dụ điển hình của công nghệ Blockchain
hiện nay chính là tiền điện tử. Nhờ có sự ra đời của ngôn ngữ lập trình Solidity, các
nhà lập trình có thể dễ dàng ứng dụng công nghệ Blockchain này vào các lĩnh vực
kinh tế và đời sống. 
2.2 Nền tảng Blockchain – Polygon (MATIC) 
- Polygon (MATIC) là một platform hỗ trợ phát triển cơ sở hạ tầng và
giúp Ethereum mở rộng quy mô hay còn gọi là layer 2. Polygon (MATIC) hỗ trợ
tất cả các công cụ Ethereum hiện có cùng với các giao dịch nhanh hơn và rẻ hơn.
Polygon (MATIC) cố gắng giải quyết các vấn đề về khả năng mở rộng và khả
năng sử dụng trong khi không ảnh hưởng đến phân quyền, bảo mật và tận dụng
cộng đồng nhà phát triển, hệ sinh thái hiện có. Polygon là một giải pháp mở rộng
off/side chain cho các nền tảng hiện có để cung cấp khả năng mở rộng và trải
nghiệm người dùng vượt trội cho DApps/ chức năng người dùng. 
- Tính năng chính và điểm nổi bật: 
- Khả năng mở rộng: giao dịch nhanh, chi phí thấp và an toàn trên các
sidechain Polygon với độ chính xác đạt được trên chuỗi chính và Ethereum là
layer 1 base chain tương thích đầu tiên. 
- Thông lượng cao: đạt được lên đến 10.000 TPS trên một single
sidechain trên testnet nội bộ. Nhiều chuỗi sẽ được thêm vào để mở rộng quy mô
theo chiều ngang. 

7
IE102.M21- Các công nghệ nền | Nhóm 4

- Trải nghiệm người dùng: mượt mà và dễ sử dụng từ chain chính đến


Polygon, ứng dụng di động gốc và SDK có hỗ trợ Wallet Connect. 
- Bảo mật: những người vận hành Polygon chain, chính họ là những
người đóng vai trò quan trọng trong hệ thống PoS. 
- Public Sidechains: Polygon sidechain về bản chất là công khai (so với
chuỗi DApp riêng lẻ), không được phép và có khả năng hỗ trợ nhiều giao thức. 
- Kiến trúc Polygon 
Gồm 4 layer có thể kết hợp: Execution layer, Polygon networks layer,
Security layer, Ethereum layer. 
 

 
Hình 2.1 Kiến trúc 4 lớp của Polygon
- Execution layer 
Layer này diễn giải và thực hiện các giao dịch đã được thỏa thuận và đưa vào
các blockchain của mạng Polygon (MATIC). Nó bao gồm hai lớp con: 
● Môi trường thực thi (triển khai máy ảo có thể cắm được) 
● Logic thực thi (chức năng chuyển đổi trạng thái của mạng Polygon,
thường được viết dưới dạng hợp đồng thông minh Ethereum) 
- Polygon networks layer 
Một tập hợp của các mạng blockchain có chủ quyền. Mỗi mạng phục vụ
cộng đồng tương ứng của nó, duy trì các chức năng như: 
● Đối chiếu giao dịch 
● Sự đồng thuận của nội bộ 
● Sản xuất khối 

8
IE102.M21- Các công nghệ nền | Nhóm 4

Các mạng có thể sử dụng giao thức Polygon (MATIC) để kết nối với nhau và
trao đổi thông tin tùy ý. 
- Security layer 
Một layer chuyên biệt, không bắt buộc phải quản lý một tập hợp các trình
xác thực và có thể kiểm tra định kỳ tính hợp lệ của bất kỳ chuỗi Polygon
(MATIC) nào với một khoản phí. Lớp này có thể được triển khai như một siêu
blockchain chạy song song với Ethereum, phụ trách các chức năng như: 
● Quản lý trình xác thực (đăng ký/ hủy đăng ký, phần thưởng…) 
● Xác thực Polygon Chain 
Lớp bảo mật là fully abstract và có thể có nhiều trường hợp, được thực hiện
bởi các thực thể khác nhau, có các đặc điểm khác nhau. Nó cũng có thể được
thực hiện trực tiếp trên Ethereum, trong trường hợp đó, các thợ đào Ethereum
thực hiện việc xác nhận. 
- Ethereum layer 
Polygon chain có thể sử dụng Ethereum, blockchain lập trình an toàn nhất
trên thế giới, để lưu trữ và thực hiện bất kỳ thành phần quan trọng nào trong
logic của chúng. Layer này được triển khai dưới dạng một tập hợp các hợp đồng
thông minh Ethereum, phụ trách các chức năng như: 
● Tính chất cuối cùng/điểm kiểm tra 
● Staking 
● Giải quyết tranh chấp 
● Chuyển tiếp thông tin 
2.3 Tiêu chuẩn ERC-721 
Tiêu chuẩn non-fungible token ERC-721 được viết bằng ngôn ngữ Solidity trên
chuỗi khối Ethereum và nó cho phép các nhà phát triển mã hóa quyền sở hữu bất kỳ
dữ liệu tùy ý nào. Đặc biệt, tiêu chuẩn này nhằm mục đích tạo ra các mã thông báo
có thể hoán đổi cho nhau. Một ví dụ về hợp đồng ER-721 là của OpenZeppelin, cho
phép các nhà phát triển theo dõi các vật phẩm trong trò chơi của họ. 

9
IE102.M21- Các công nghệ nền | Nhóm 4

Hình 2.2 Mã thông báo ERC-721


2.4 Frontend Frameworks 
2.4.1 Next.js 
- Next.js là một framework dùng để phát triển các ứng dụng React.
React là một framework của javascript để xây dựng frontend với nhiều ưu điểm
như chia nhỏ các phần của web, module hoá css, xử lý sự kiện…Tuy nhiên,
react chỉ phù hợp để render dữ liệu phía client, việc này ảnh hưởng rất lớn đến
SEO của website khi mà hầu hết những data mà google đánh index đều phải
được render ở server. 
- Phân biệt giữa Serverside Rendering và Clientside Rendering 
Serverside Rendering Clientside Rendering
Server trả về cho browser file CSR trả về file HTML gần như trống cùng với
HTML của trang đã được link tới file JS 
rendered 
Nội dung file HTML đã hoàn Đối với CSR, ta sẽ cần chờ tới khi tất cả các quá
chỉnh và được hiển thị ngay khi trình xây dựng Virtual DOM và gắn các sự kiện
nó được load về máy  hoàn thành đến khi Virtual DOM được chuyển
vào browser DOM để website có thể xem được. 

Bảng 2.1 So sánh Serverside rendering và Clientside rendering

10
IE102.M21- Các công nghệ nền | Nhóm 4

- Để hỗ trợ việc render ở phía server cho React, các nhà phát triển đã tạo ra
Next.js. Next.js tích hợp nhiều tính năng như:  
- Pre-rendering, cả static generation (SSG) và server-side rendering (SSR),  
- Tách mã tự động để tải trang nhanh hơn, mỗi page chỉ load những gì cần
thiết cho page đó. Ví dụ khi render trang chủ thì những page khác sẽ không
được khởi tạo, như vậy trang chủ sẽ được load nhanh cho dù website của bạn
có cả 100 page 
- Hỗ trợ refresh page nhanh chóng ở môi trường development 
- Hoàn toàn có thể mở rộng 
2.4.2 CSS Tailwind 
Tailwind là một CSS framework giúp ta có thể nhanh chóng xây dựng giao diện
người dùng. Tailwind giúp xây dựng website một cách nhanh chóng nhất với các
thuộc tính CSS đã được gán thành những class riêng và khi dùng ta chỉ việc gọi ra
sử dụng.  
2.5 Ethereum Development Environment – Hardhat 
- Hardhat là một môi trường để biên dịch, triển khai, test và debug dapp
ethereum được phát triển trên nền tảng javascript. 
- Một số điểm tính năng nổi bật của Hardhat 
- Tích hợp mạng local Hardhat, dễ dàng chạy và debug code ngay trên local. 
- Debug dễ dàng hơn: Với Hardhat, chúng ta có thể debug code Solidity dễ
dàng hơn khi có thể console.log ra các biến (Solidity vốn ko hỗ trợ console.log) 
- Hệ thống plugin: Giúp developer có thể bổ sung chức năng, tùy vào từng dự
án cụ thể 
- Hỗ trợ TypeScript 
- So sánh Hardhat và Truffle 
Hardhat Truffle
Tài liệu hướng dẫn rõ ràng và được cấu Có tài liệu hướng dẫn rõ ràng, nhiều tài
trúc tốt, phù hợp cho người mới  liệu tham khảo trên các web, blog,
youtube… 
Hardhat có một cộng đồng rất sôi nổi Truffle có cộng đồng hùng mạnh và sôi
bao gồm các nhà phát triển blockchain nổi nhờ ra đời sớm hơn. 
và các thành viên cốt lõi của
framework. 
Hardhat có rất nhiều thư viện và plugin, Truffle cũng có plugin hoặc thư viện gọi
người dùng có thể tự tạo plugin cho là Box, người dùng cũng có thể tự tạo
mình.  Box cho mình nhưng cách dùng phức tạp
hơn. 

11
IE102.M21- Các công nghệ nền | Nhóm 4

Môi trường thử nghiệm linh hoạt hơn do Thử nghiệm không thể tương tác với
có thể tương tác trực tiếp với smart contract. 
contract. 

Bảng 2.2 So sánh Truffle và Hardhat


2.6 Storage Platforms - IPFS 
- IPFS là viết tắt của từ Interplanetary File System, một hệ thống tập tin phân
tán ngang hàng kết nối tất cả các thiết bị máy tính với nhau. Cụ thể hơn, nó sẽ
phân phối dữ liệu được lưu trữ theo hình thức P2P, hay còn gọi là mạng ngang
hàng. 
- Trong đó, các hoạt động của IPFS chủ yếu dựa vào khả năng tính toán băng
thông của tất cả các máy tham gia chứ không tập trung vào một phần nhỏ các
máy chủ trung tâm như giao thức HTTP. 
- IPFS là mạng lưới chuyển phát nội dung hoàn toàn phi tập trung cho phép
quản lý và lưu trữ dữ liệu một cách linh hoạt. Mỗi máy tính tham gia trong mạng
lưới đảm nhận nhiệm vụ download và upload dữ liệu mà không cần sự can thiệp
của máy chủ trung tâm. 
- Đầu tiên mọi dữ liệu sẽ được mã hoá và được lưu dưới dạng mã hash (còn
gọi là đối tượng IPFS). Ý tưởng chủ đạo là nếu trình duyệt của bạn muốn truy
cập một trang nào đó trên IPFS thì chỉ cần đưa ra mã hash rồi mạng sẽ tìm máy
có lưu trữ dữ liệu khớp với mã hash và sau đó tải dữ liệu, trang đó về từ máy
tính đấy về cho bạn. 
- Mỗi máy tính tham gia trong mạng lưới của nó sẽ đảm nhận cả việc
download lẫn upload dữ liệu mà không cần có sự có mặt của một máy chủ trung
tâm. Tổng quan, cách hoạt động của IPFS sẽ có 2 phần chính: 
- Xác định tệp có địa chỉ nội dung (giá trị hash của tệp đó). 
- Tìm dữ liệu được lưu trữ và tải xuống: khi bạn có đoạn hash của file hay
trang cần tải, mạng sẽ tìm và connect tới máy tốt nhất để tải dữ liệu xuống
cho bạn. 
2.7 Ethereum Web Client Library - Ethers.js 
Ethers.js là một thư viện được viết bằng javascript giúp dapp tương tác được với
mạng Ethereum Blockchain. 
Các tính năng nổi bật Ethers.js gồm có: 
- Giữ private key ở client một cách an toàn.
- Import và export JSON wallets.
- Import và export ví theo chuẩn BIP 39.
- Hỗ trợ ABI, ABIv2 và Human-Readable ABI.

12
IE102.M21- Các công nghệ nền | Nhóm 4

- Kết nối với Ethereum nodes thông qua nhiều provider như JSON-RPC,
INFURA, Etherscan, Alchemy, Cloudflare, MetaMask ... 
- Hỗ trợ ENS.
- Nhẹ (88kb khi nén và 284kb khi không nén).
- Hỗ trợ TypeScript.

CHƯƠNG 3: THIẾT KẾ HỆ THỐNG

Hình 2.3 Sơ đồ thiết kế hệ thống


Các thành phần và kiến trúc của NFT Marketplace
3.1 Các thành phần
3.1.1 Các thành phần kiến trúc
- Trang web NFT Marketplace là một ứng dụng web nơi người dùng bán và
mua NFT. 
- Metamask là một ví điện tử cung cấp dịch vụ trực tuyến để lưu trữ các quỹ
kỹ thuật số.
- Siêu dữ liệu NFT là mô tả của tài sản kỹ thuật số được bán dưới dạng NFT,
cụ thể là tên, ngày tạo, thuộc tính, chủ sở hữu, v.v.
- IPFS  là một hệ thống lưu trữ dữ liệu tránh tiêu tốn điện năng lưu trữ tài sản
kỹ thuật số với siêu dữ liệu trên blockchain.
- Hợp đồng thông minh tạo ra một số nhận dạng duy nhất cho mỗi NFT. Các
hợp đồng thông minh cho thị trường NFT sử dụng tiêu chuẩn ERC-721 để
tạo các mã thông báo không thể thay thế, giúp phân biệt NFT với các mã
thông báo tiền điện tử có thể thay thế.

13
IE102.M21- Các công nghệ nền | Nhóm 4

- Blockchain là một cơ sở dữ liệu phân tán lưu trữ thông tin về các giao dịch
NFT.
3.1.2 Công nghệ và dịch vụ của bên thứ ba
- Solidity là một ngôn ngữ lập trình cho các hợp đồng thông minh.
- Infura.io là một dịch vụ cung cấp quyền truy cập vào chuỗi khối Ethereum.
3.2 Các bước thực hiện 
- Người dùng tải lên nội dung kỹ thuật số và điền vào siêu dữ liệu
- Siêu dữ liệu đã được xác minh và gửi tới bộ lưu trữ dữ liệu bên ngoài
- Một mã nhận dạng duy nhất được chỉ định cho nội dung và một NFT được
tạo ra
- NFT được thêm vào blockchain

14
IE102.M21- Các công nghệ nền | Nhóm 4

CHƯƠNG 4: CÀI ĐẶT MÔI TRƯỜNG


4.1 Tạo một ứng dụng Next.js 
npx create-next-app nft-marketplace
4.2 Cài đặt các dependencies cho ứng dụng
cd nft-marketplace

npm install ethers hardhat @nomiclabs/hardhat-waffle /


ethereum-waffle chai @nomiclabs/hardhat-ethers /
web3modal @openzeppelin/contracts ipfs-http-client /
axios
4.3 Cài đặt framework Tailwind
Thiết lập dependencies
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
Tạo các tệp cấu hình cần thiết để Tailwind hoạt động với Next.js
npx tailwindcss init -p
Cấu hình lại đường dẫn trong file tailwind.config.js
/* tailwind.config.js */
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
Xóa mã trong styles/ global.css và cập nhật tailwind vào ứng dụng
@tailwind base;
@tailwind components;
@tailwind utilities;
4.4 Cấu hình Hardhat
Khởi tạo một môi trường phát triển Hardhat mới từ gốc của dự án
npx hardhat

? What do you want to do? Create a basic sample project

15
IE102.M21- Các công nghệ nền | Nhóm 4

?Hardhat project root: <Choose default path>

(Lưu ý: Nếu gặp lỗi khi tham chiếu đến tệp README.md, xóa README.md
và chạy lại npx hardhat.)
Sau khi chạy lệnh, một số tệp và thư mục mới sẽ được thêm vào thư mục gốc
hardhat.config.js - Chứa toàn bộ thiết lập Hardhat của ứng dụng (cấu hình,
plugin và các tác vụ tùy chỉnh).
scripts - Một thư mục chứa một tập lệnh có tên là sample-script.js sẽ triển
khai hợp đồng thông minh khi ứng dụng được thực thi.
test - Một thư mục chứa một tập lệnh thử nghiệm mẫu.
contracts - Một thư mục chứa một hợp đồng thông minh Solidity.
Cấu hình lại tệp hardhat.config.js  
/* hardhat.config.js */
require("@nomiclabs/hardhat-waffle")

module.exports = {
  defaultNetwork: "hardhat",
  networks: {
    hardhat: {
      chainId: 1337
    },
// kết nối với munbai testnet
//  unused configuration commented out for now
//  mumbai: {
//    url: "https://rpc-mumbai.maticvigil.com",
//    accounts: [process.env.privateKey]
//  }
  },
  solidity: {
    version: "0.8.4",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  }
}

16
IE102.M21- Các công nghệ nền | Nhóm 4

17
IE102.M21- Các công nghệ nền | Nhóm 4

CHƯƠNG 5: PHÂN TÍCH TRANG WEB


5.1 Smart contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/utils/Counters.sol";
import
"@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
 
import "hardhat/console.sol";
 
contract NFTMarketplace is ERC721URIStorage {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;
    Counters.Counter private _itemsSold;
 
    uint256 listingPrice = 0.025 ether;
    address payable owner;
 
    mapping(uint256 => MarketItem) private idToMarketItem;
 
    struct MarketItem {
      uint256 tokenId;
      address payable seller;
      address payable owner;
      uint256 price;
      bool sold;
    }
 
    event MarketItemCreated (
      uint256 indexed tokenId,
      address seller,
      address owner,
      uint256 price,
      bool sold
    );
 

18
IE102.M21- Các công nghệ nền | Nhóm 4

    constructor() ERC721("Metaverse Tokens", "METT") {


      owner = payable(msg.sender);
    }
 
    /* Cập nhật giá niêm yết của hợp đồng*/
    function updateListingPrice(uint _listingPrice) public payable {
      require(owner == msg.sender, "Only marketplace owner can update listing
price.");
      listingPrice = _listingPrice;
    }
 
    /* Trả về giá niêm yết của hợp đồng*/
    function getListingPrice() public view returns (uint256) {
      return listingPrice;
    }
 
    /* Tạo Token và thêm nó vào marketplace */
    function createToken(string memory tokenURI, uint256 price) public payable
returns (uint) {
      _tokenIds.increment();
      uint256 newTokenId = _tokenIds.current();
 
      _mint(msg.sender, newTokenId);
      _setTokenURI(newTokenId, tokenURI);
      createMarketItem(newTokenId, price);
      return newTokenId;
    }
 
    function createMarketItem(
      uint256 tokenId,
      uint256 price
    ) private {
      require(price > 0, "Price must be at least 1 wei");
      require(msg.value == listingPrice, "Price must be equal to listing price");
 
      idToMarketItem[tokenId] =  MarketItem(
        tokenId,
        payable(msg.sender),

19
IE102.M21- Các công nghệ nền | Nhóm 4

        payable(address(this)),
        price,
        false
      );
 
      _transfer(msg.sender, address(this), tokenId);
      emit MarketItemCreated(
        tokenId,
        msg.sender,
        address(this),
        price,
        false
      );
    }
 
    /* cho phép ai đó bán lại token mà họ đã mua*/
    function resellToken(uint256 tokenId, uint256 price) public payable {
      require(idToMarketItem[tokenId].owner == msg.sender, "Only item owner can
perform this operation");
      require(msg.value == listingPrice, "Price must be equal to listing price");
      idToMarketItem[tokenId].sold = false;
      idToMarketItem[tokenId].price = price;
      idToMarketItem[tokenId].seller = payable(msg.sender);
      idToMarketItem[tokenId].owner = payable(address(this));
      _itemsSold.decrement();
 
      _transfer(msg.sender, address(this), tokenId);
    }
 
    /* Tạo chức năng mua bán một marketplace item */
    /* Chuyển quyền sở hữu item, cũng như tiền giữa các bên tham gia mua bán*/
    function createMarketSale(
      uint256 tokenId
      ) public payable {
      uint price = idToMarketItem[tokenId].price;
      address seller = idToMarketItem[tokenId].seller;
      require(msg.value == price, "Please submit the asking price in order to complete
the purchase");

20
IE102.M21- Các công nghệ nền | Nhóm 4

      idToMarketItem[tokenId].owner = payable(msg.sender);
      idToMarketItem[tokenId].sold = true;
      idToMarketItem[tokenId].seller = payable(address(0));
      _itemsSold.increment();
      _transfer(address(this), msg.sender, tokenId);
      payable(owner).transfer(listingPrice);
      payable(seller).transfer(msg.value);
    }
 
    /* Trả lại tất cả các item chưa bán được trên market*/
    function fetchMarketItems() public view returns (MarketItem[] memory) {
      uint itemCount = _tokenIds.current();
      uint unsoldItemCount = _tokenIds.current() - _itemsSold.current();
      uint currentIndex = 0;
 
      MarketItem[] memory items = new MarketItem[](unsoldItemCount);
      for (uint i = 0; i < itemCount; i++) {
        if (idToMarketItem[i + 1].owner == address(this)) {
          uint currentId = i + 1;
          MarketItem storage currentItem = idToMarketItem[currentId];
          items[currentIndex] = currentItem;
          currentIndex += 1;
        }
      }
      return items;
    }
 
    /* Chỉ trả lại các item mà người dùng đã mua*/
    function fetchMyNFTs() public view returns (MarketItem[] memory) {
      uint totalItemCount = _tokenIds.current();
      uint itemCount = 0;
      uint currentIndex = 0;
 
      for (uint i = 0; i < totalItemCount; i++) {
        if (idToMarketItem[i + 1].owner == msg.sender) {
          itemCount += 1;
        }
      }

21
IE102.M21- Các công nghệ nền | Nhóm 4

 
      MarketItem[] memory items = new MarketItem[](itemCount);
      for (uint i = 0; i < totalItemCount; i++) {
        if (idToMarketItem[i + 1].owner == msg.sender) {
          uint currentId = i + 1;
          MarketItem storage currentItem = idToMarketItem[currentId];
          items[currentIndex] = currentItem;
          currentIndex += 1;
        }
      }
      return items;
    }
 
    /* Chỉ trả lại các item mà người dùng đã liệt kê*/
    function fetchItemsListed() public view returns (MarketItem[] memory) {
      uint totalItemCount = _tokenIds.current();
      uint itemCount = 0;
      uint currentIndex = 0;
 
      for (uint i = 0; i < totalItemCount; i++) {
        if (idToMarketItem[i + 1].seller == msg.sender) {
          itemCount += 1;
        }
      }
 
      MarketItem[] memory items = new MarketItem[](itemCount);
      for (uint i = 0; i < totalItemCount; i++) {
        if (idToMarketItem[i + 1].seller == msg.sender) {
          uint currentId = i + 1;
          MarketItem storage currentItem = idToMarketItem[currentId];
          items[currentIndex] = currentItem;
          currentIndex += 1;
        }
      }
      return items;
    }
}

22
IE102.M21- Các công nghệ nền | Nhóm 4

5.2 Màn hình trang chủ


5.2.1 Màn hình và chức năng
● Màn hình:

Hình 5.1 Màn hình trang chủ.


● Chức năng:

STT Thành phần Mô tả

1 Infinity Marketplace Tên của trang

2 Thanh Navbar Điều hướng đến các trang: Home, Sell NFT, My
NFTs, Dashboard 

3 Thông tin ví người Hiển thị số tiền và địa chỉ tài khoản người dùng
dùng

4 Danh sách sản phẩm Hiển thị những sản phẩm đang bán trên sàn

5 Nút Buy Mua và thanh toán sản phẩm thông qua ví metamask

6 Nút Detail Xem chi tiết thông tin sản phẩm

Bảng 5.1 Bảng chức năng trang chủ.


5.2.2 Code
/* pages/index.js */
import { ethers } from "ethers";
import { useEffect, useState } from "react";
import axios from "axios";

23
IE102.M21- Các công nghệ nền | Nhóm 4

import Web3Modal from "web3modal";


import { useRouter } from "next/router";
 
import { marketplaceAddress } from "../config";
 
import NFTMarketplace from
"../artifacts/contracts/NFTMarketplace.sol/NFTMarketplace.json";
export default function Home() {
  const [searchTerm, setSearchTerm] = useState("");
  const [nfts, setNfts] = useState([]);
  const [loadingState, setLoadingState] = useState("not-loaded");
  useEffect(() => {
    loadNFTs();
  }, []);
 
  const router = useRouter();
  useEffect(() => {
    loadNFTs();
  }, []);
 
  async function loadNFTs() {
   /* tạo một nhà cung cấp chung và truy vấn các item chưa bán được trên                                   
market*/
    const provider = new ethers.providers.JsonRpcProvider(
     "https://polygon-mumbai.infura.io/v3/88ae0db50107404793a217cdb3e29de4"
    );
    const contract = new ethers.Contract(
      marketplaceAddress,
      NFTMarketplace.abi,
      provider
    );
    const data = await contract.fetchMarketItems();
 
    /*
     *  map over các item được trả về từ smart contract and format
     *  chúng được biết như là fetch token metadata
     */
    const items = await Promise.all(

24
IE102.M21- Các công nghệ nền | Nhóm 4

      data.map(async (i) => {


        const tokenUri = await contract.tokenURI(i.tokenId);
        const meta = await axios.get(tokenUri);
        let price = ethers.utils.formatUnits(i.price.toString(), "ether");
        let item = {
          price,
          tokenId: i.tokenId.toNumber(),
          seller: i.seller,
          owner: i.owner,
          image: meta.data.image,
          name: meta.data.name,
          description: meta.data.description,
          tokenURI: tokenUri,
        };
        return item;
      })
    );
    setNfts(items);
    setLoadingState("loaded");
  }
  async function buyNft(nft) {
    /* cần người dùng đăng ký vào giao dịch, vì vậy sẽ sử dụng Web3Provider và
đăng ký*/
    const web3Modal = new Web3Modal();
    const connection = await web3Modal.connect();
    const provider = new ethers.providers.Web3Provider(connection);
    const signer = provider.getSigner();
    const contract = new ethers.Contract(
      marketplaceAddress,
      NFTMarketplace.abi,
      signer
    );
 
    /* người dùng sẽ được nhắc thanh toán các thủ tục yêu cầu để hoàn thành giao
dịch*/
    const price = ethers.utils.parseUnits(nft.price.toString(), "ether");
    const transaction = await contract.createMarketSale(nft.tokenId, {
      value: price,

25
IE102.M21- Các công nghệ nền | Nhóm 4

    });
    await transaction.wait();
    loadNFTs();
  }
  function detailNFT(nft) {
    router.push(`/detail-nft?id=${nft.tokenId}&tokenURI=$
{nft.tokenURI}&prev=`);
  }
 
  if (loadingState === "loaded" && !nfts.length)
    return <h1 className="px-20 py-10 text-3xl">No items in marketplace</h1>;
  return (
    <div>
      <div className="w-full m-3 max-w-xs border-solid border-2 border-gray">
        <input
          className="shadow appearance-none border rounded w-full py-2 px-3 text-
gray-700 leading-tight focus:outline-none focus:shadow-outline"
          type="search"
          placeholder="Search..."
          onChange={(event) => {
            setSearchTerm(event.target.value);
          }}
        />
      </div>
 
      <div className="flex">
        <div className="px-4" style={{ maxWidth: "1600px" }}>
          <h2 className="text-2xl py-2">Marketplace</h2>
          <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 pt-4">
            {nfts
              .filter((nft) =>
                nft.name.toLowerCase().includes(searchTerm.toLowerCase())
              )
              .map((nft, i) => (
                <div
                  key={i}
                  className="border shadow rounded-xl overflow-hidden flex flex-col"
                >

26
IE102.M21- Các công nghệ nền | Nhóm 4

                  <div className="h-1/2 bg-cover">


                    <img src={nft.image} />
                  </div>
                  <div className="h-1/4 p-4">
                    <p
                      style={{ height: "64px" }}
                      className="text-2xl font-semibold"
                    >
                      {nft.name}
                    </p>
                    <div style={{ height: "70px", overflow: "hidden" }}>
                      <p className="text-gray-400">{nft.description}</p>
                    </div>
                  </div>
                  <div className="h-1/4 p-4 bg-black">
                    <p className="text-2xl font-bold text-white">
                      {nft.price} ETH
                    </p>
                    <button
                      className="mt-4 w-full bg-pink-500 text-white font-bold py-2 px-12
rounded"
                      onClick={() => buyNft(nft)}
                    >
                      Buy
                    </button>
                    <button
                      className="mt-4 w-full bg-pink-500 text-white font-bold py-2 px-12
rounded"
                      onClick={() => detailNFT(nft)}
                    >
                      Detail
                    </button>
                  </div>
                </div>
              ))}
          </div>
        </div>
      </div>

27
IE102.M21- Các công nghệ nền | Nhóm 4

    </div>
  );

5.3 Màn hình Sell NFT


5.3.1 Màn hình và chức năng
● Màn hình:

Hình 5.2 Màn hình trang Sell NFT.


● Chức năng

STT Thành phần  Mô tả

1 Asset Name Nhập tên sản phẩm

2 Asset Nhập mô tả cho sản phẩm


Description

3 Asset Price in Nhập giá sản phẩm


Eth

4 Choose File Tải ảnh của sản phẩm lên

5 Create NFT Nút bán sản phẩm 


Bảng 5.2 Bảng chức năng trang Sell NFT.
5.3.2 Code
/* pages/create-nft.js */
import { useState } from 'react'
import { ethers } from 'ethers'

28
IE102.M21- Các công nghệ nền | Nhóm 4

import { create as ipfsHttpClient } from 'ipfs-http-client'


import { useRouter } from 'next/router'
import Web3Modal from 'web3modal'
 
const client = ipfsHttpClient('https://ipfs.infura.io:5001/api/v0')
 
import {
  marketplaceAddress
} from '../config'
 
import NFTMarketplace from
'../artifacts/contracts/NFTMarketplace.sol/NFTMarketplace.json'
 
export default function CreateItem() {
  const [fileUrl, setFileUrl] = useState(null)
  const [formInput, updateFormInput] = useState({ price: '', name: '', description:
'' })
  const router = useRouter()
 
  async function onChange(e) {
    /* tải image đến IPFS */
    const file = e.target.files[0]
    try {
      const added = await client.add(
        file,
        {
          progress: (prog) => console.log(`received: ${prog}`)
        }
      )
      const url = `https://ipfs.infura.io/ipfs/${added.path}`
      setFileUrl(url)
    } catch (error) {
      console.log('Error uploading file: ', error)
    }  
  }
  async function uploadToIPFS() {
    const { name, description, price } = formInput
    if (!name || !description || !price || !fileUrl) return

29
IE102.M21- Các công nghệ nền | Nhóm 4

    /* đầu tiên, tải metadata đến IPFS */


    const data = JSON.stringify({
      name, description, image: fileUrl
    })
    try {
      const added = await client.add(data)
      const url = `https://ipfs.infura.io/ipfs/${added.path}`
      /* sau khi metadata được uploaded đến IPFS, trở về URL để sử dụng nó trong
giao dịch */
      return url
    } catch (error) {
      console.log('Error uploading file: ', error)
    }  
  }
 
  async function listNFTForSale() {
    const url = await uploadToIPFS()
    const web3Modal = new Web3Modal()
    const connection = await web3Modal.connect()
    const provider = new ethers.providers.Web3Provider(connection)
    const signer = provider.getSigner()
 
    /* tạo NFT */
    const price = ethers.utils.parseUnits(formInput.price, 'ether')
    let contract = new ethers.Contract(marketplaceAddress, NFTMarketplace.abi,
signer)
    let listingPrice = await contract.getListingPrice()
    listingPrice = listingPrice.toString()
    let transaction = await contract.createToken(url, price, { value: listingPrice })
    await transaction.wait()
 
    router.push('/')
  }
 
  return (
    <div className="flex justify-center">
      <div className="w-1/2 flex flex-col pb-12">
        <input

30
IE102.M21- Các công nghệ nền | Nhóm 4

          placeholder="Asset Name"
          className="mt-8 border rounded p-4"
          onChange={e => updateFormInput({ ...formInput, name: e.target.value })}
        />
        <textarea
          placeholder="Asset Description"
          className="mt-2 border rounded p-4"
          onChange={e => updateFormInput({ ...formInput, description:
e.target.value })}
        />
        <input
          placeholder="Asset Price in Eth"
          className="mt-2 border rounded p-4"
          onChange={e => updateFormInput({ ...formInput, price: e.target.value })}
        />
        <input
          type="file"
          name="Asset"
          className="my-4"
          onChange={onChange}
        />
        {
          fileUrl && (
            <img className="rounded mt-4" width="350" src={fileUrl} />
          )
        }
        <button onClick={listNFTForSale} className="font-bold mt-4 bg-pink-500
text-white rounded p-4 shadow-lg">
          Create NFT
        </button>
      </div>
    </div>
  )
}
5.4 Màn hình My NFTs
5.4.1 Màn hình và chức năng:
● Màn hình

31
IE102.M21- Các công nghệ nền | Nhóm 4

Hình 5.3 Màn hình trang My NFTs.


● Chức năng:

STT Thành phần Mô tả

1 My NFTs Hiển thị các NFT đã sở hữu

2 Thanh Tìm kiếm NFT trong danh sách sở hữu


Search

3 Nút List Chuyển đến trang Resell NFT để thực hiện bán lại NFT đang
sở hữu

4 Nút Detail Xem chi tiết thông tin sản phẩm


Bảng 5.3 Bảng chức năng trang Sell NFTs.
5.4.2 Code:
/* pages/my-nfts.js */
import { ethers } from 'ethers'
import { useEffect, useState } from 'react'
import axios from 'axios'
import Web3Modal from 'web3modal'
import { useRouter } from 'next/router'
 
import {
  marketplaceAddress
} from '../config'

32
IE102.M21- Các công nghệ nền | Nhóm 4

 
import NFTMarketplace from
'../artifacts/contracts/NFTMarketplace.sol/NFTMarketplace.json'
 
export default function MyAssets() {
  const [searchTerm, setSearchTerm] = useState("");
  const [nfts, setNfts] = useState([])
  const [loadingState, setLoadingState] = useState('not-loaded')
  const router = useRouter()
  useEffect(() => {
    loadNFTs()
  }, [])
/* tải NFT */
  async function loadNFTs() {
    const web3Modal = new Web3Modal({
      network: "mainnet",
      cacheProvider: true,
    })
    const connection = await web3Modal.connect()
    const provider = new ethers.providers.Web3Provider(connection)
    const signer = provider.getSigner()
 
    const marketplaceContract = new ethers.Contract(marketplaceAddress,
NFTMarketplace.abi, signer)
    const data = await marketplaceContract.fetchMyNFTs()
 
    const items = await Promise.all(data.map(async i => {
      const tokenURI = await marketplaceContract.tokenURI(i.tokenId)
      const meta = await axios.get(tokenURI)
      let price = ethers.utils.formatUnits(i.price.toString(), 'ether')
      let item = {
        price,
        tokenId: i.tokenId.toNumber(),
        seller: i.seller,
        owner: i.owner,
        image: meta.data.image,
        description: meta.data.description,
        name: meta.data.name,

33
IE102.M21- Các công nghệ nền | Nhóm 4

        tokenURI
      }
      return item
    }))
    setNfts(items)
    setLoadingState('loaded')
  }
/* List NFT */
  function listNFT(nft) {
    router.push(`/resell-nft?id=${nft.tokenId}&tokenURI=${nft.tokenURI}`)
  }
  function detailNFT(nft) {
    router.push(`/detail-nft?id=${nft.tokenId}&tokenURI=$
{nft.tokenURI}&prev=my-nfts`)
  }
  if (loadingState === 'loaded' && !nfts.length) return (<h1 className="py-10 px-
20 text-3xl">No NFTs owned</h1>)
  return (
    <div>
    <div class="w-full  m-3 max-w-xs border-solid border-2 border-gray">
     <input class="shadow appearance-none border rounded w-full py-2 px-3 text-
gray-700 leading-tight focus:outline-none focus:shadow-outline" type="search"
placeholder="Search..."
      onChange={(event) => {
        setSearchTerm(event.target.value)
      } } />
    </div>
    <div className="flex">
      <div className="p-4" style={{ maxWidth: '1600px' }}>
      <h2 className="text-2xl py-2">My NFTs</h2>
        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 pt-4">
          {
            nfts.filter(nft =>
nft.name.toLowerCase().includes(searchTerm.toLowerCase())).map((nft, i) => (
              <div key={i} className="border shadow rounded-xl overflow-hidden">
                <img src={nft.image} className="rounded" />
                <div className="p-4">

34
IE102.M21- Các công nghệ nền | Nhóm 4

                  <p style={{ height: '64px' }} className="text-2xl font-


semibold">{nft.name}</p>
                  <div style={{ height: '70px', overflow: 'hidden' }}>
                    <p className="text-gray-400">{nft.description}</p>
                  </div>
                </div>
                <div className="p-4 bg-black">
                  <button className="mt-4 w-full bg-pink-500 text-white font-bold py-2
px-12 rounded" onClick={() => listNFT(nft)}>List</button>
                  <button className="mt-4 w-full bg-pink-500 text-white font-bold py-2
px-12 rounded" onClick={() => detailNFT(nft)}>Detail</button>
                </div>
              </div>
            ))
          }
        </div>
      </div>
    </div>
    </div>
  )
}
5.5 Màn hình Detail NFT
5.5.1 Màn hình và chức năng:
● Màn hình

35
IE102.M21- Các công nghệ nền | Nhóm 4

Hình 5.4 Màn hình trang Detail NFT.


● Chức năng:

STT Thành phần Mô tả

1 Name Tên của NFT

2 Description Mô tả NFT

3 Token ID Mã token

4 Price Giá của NFT

5 Owner Người sở hữu NFT

6 Seller Người bán NFT

36
IE102.M21- Các công nghệ nền | Nhóm 4

7 Nút Back Quay lại trang  trước


Bảng 5.5 Bảng chức năng trang Detail NFT.
5.5.2 Code:
/* pages/detail-nft.js */
import { useEffect, useState } from 'react'
import { ethers } from 'ethers'
import { useRouter } from 'next/router'
import axios from 'axios'
import Web3Modal from 'web3modal'
 
import {
  marketplaceAddress
} from '../config'
 
import NFTMarketplace from
'../artifacts/contracts/NFTMarketplace.sol/NFTMarketplace.json'
 
export default function DetailNFT() {
  const [nfts, setNfts] = useState([])
  const [myNfts, setMyNfts] = useState([])
  const [loadingState, setLoadingState] = useState('not-loaded')
  const [formInput, updateFormInput] = useState({ price: '', image: '', name: '',
description: ''})
  const router = useRouter()
  const { id, tokenURI, prev } = router.query
  const { image, price, name, description } = formInput
 
  useEffect(() => {
    loadNFTs()
    loadMyNFTs()
    fetchNFT()
   
  }, [id])
 
  async function loadNFTs() {
    //Lấy dữ liệu NFTs trên MarketPlace.
    
    const provider = new ethers.providers.JsonRpcProvider()

37
IE102.M21- Các công nghệ nền | Nhóm 4

    const contract = new ethers.Contract(marketplaceAddress, NFTMarketplace.abi,


provider)
    const data = await contract.fetchMarketItems()
 
    const items = await Promise.all(data.map(async i => {
      const tokenUri = await contract.tokenURI(i.tokenId)
      const meta = await axios.get(tokenUri)
      let price = ethers.utils.formatUnits(i.price.toString(), 'ether')
      let item = {
        price,
        tokenId: i.tokenId.toNumber(),
        seller: i.seller,
        owner: i.owner,
        image: meta.data.image,
        name: meta.data.name,
        description: meta.data.description,
        tokenURI : tokenUri
      }
      return item
    }))
    setNfts(items)
   
    setLoadingState('loaded')
  }
   //Lấy dữ liệu NFTs trên của người dùng.
  async function loadMyNFTs() {
    const web3Modal = new Web3Modal({
      network: "mainnet",
      cacheProvider: true,
    })
    const connection = await web3Modal.connect()
    const provider = new ethers.providers.Web3Provider(connection)
    const signer = provider.getSigner()
 
    const marketplaceContract = new ethers.Contract(marketplaceAddress,
NFTMarketplace.abi, signer)
    const data = await marketplaceContract.fetchMyNFTs()
 

38
IE102.M21- Các công nghệ nền | Nhóm 4

    const items = await Promise.all(data.map(async i => {


      const tokenURI = await marketplaceContract.tokenURI(i.tokenId)
      const meta = await axios.get(tokenURI)
      let price = ethers.utils.formatUnits(i.price.toString(), 'ether')
      let item = {
        price,
        tokenId: i.tokenId.toNumber(),
        seller: i.seller,
        owner: i.owner,
        image: meta.data.image,
        description: meta.data.description,
        name: meta.data.name,
        tokenURI
      }
      return item
    }))
    setMyNfts(items)
    setLoadingState('loaded')
  }
 
  
  async function fetchNFT() {
    if (!tokenURI) return
    const meta = await axios.get(tokenURI)
    updateFormInput(state => ({ ...state, image: meta.data.image, name:
meta.data.name, description: meta.data.description }))
  }
  //Quay lại trang trước.
  function back() {
    router.push(`/${prev}`)
  }
 
 
 
  return (
    <div className="flex justify-center">
      <div className="w-1/2 flex flex-col pb-12">

39
IE102.M21- Các công nghệ nền | Nhóm 4

      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 pt-


4"></div>
      <img className="rounded mt-4 grid-cols-1" width="100%" src={image} />
      <div className="border shadow rounded-xl overflow-hidden">
        <p className="text-xl font-bold text-black">Detail NFT: </p>
        <p className="text-xl text-black"> Name: {name} </p>
        <p className="text-xl text-black"> Description: {description} </p>
        {
        nfts.filter(nft => nft.tokenId == id).map((nft, i) => (
            <div>
            <p className="text-xl text-black">Token ID: {nft.tokenId}</p>
            <p className="text-xl text-black">Price: {nft.price} ETH</p>
            <p className="text-xl text-black">Owner: {nft.owner}</p>
            <p className="text-xl text-black">Seller: {nft.seller}</p>
            </div>
        ))
       
        }
        {
        myNfts.filter(nft => nft.tokenId == id).map((nft, i) => (
            <div>
            <p className="text-xl text-black">Token ID: {nft.tokenId}</p>
            <p className="text-xl text-black">Price: {nft.price} ETH</p>
            <p className="text-xl text-black">Owner: {nft.owner}</p>
            <p className="text-xl text-black">Seller: {nft.seller}</p>
            </div>
        ))
        }
      </div>
 
      <button onClick={back} className="font-bold mt-4 bg-pink-500 text-white
rounded p-4 shadow-lg">
          Back
      </button>
      </div>
    </div>
  )
}

40
IE102.M21- Các công nghệ nền | Nhóm 4

5.6 Màn hình Resell NFT


5.6.1Màn hình và chức năng:
● Màn hình

Hình 5.5 Màn hình trang Resell NFT.


● Chức năng:

STT Thành phần Mô tả

1 Asset price in Một trường cho phép nhập giá của NFT (tính bằng Ethereum)
Eth để bán lại NFT này

2 Nút List NFT Đưa NFT vào danh sách bán


Bảng 5.5 Bảng chức năng trang Resell NFT.
5.6.2 Code:
/* pages/resell-nft.js */
import { useEffect, useState } from 'react'
import { ethers } from 'ethers'
import { useRouter } from 'next/router'
import axios from 'axios'
import Web3Modal from 'web3modal'
 

41
IE102.M21- Các công nghệ nền | Nhóm 4

import {
  marketplaceAddress
} from '../config'
 
import NFTMarketplace from
'../artifacts/contracts/NFTMarketplace.sol/NFTMarketplace.json'
 
export default function ResellNFT() {
  const [formInput, updateFormInput] = useState({ price: '', image: '' })
  const router = useRouter()
  const { id, tokenURI } = router.query
  const { image, price } = formInput
 
  useEffect(() => {
    fetchNFT()
  }, [id])
 
  async function fetchNFT() {
    if (!tokenURI) return
    const meta = await axios.get(tokenURI)
    updateFormInput(state => ({ ...state, image: meta.data.image }))
  }
/* List các NFT dùng để mua bán */
  async function listNFTForSale() {
    if (!price) return
    const web3Modal = new Web3Modal()
    const connection = await web3Modal.connect()
    const provider = new ethers.providers.Web3Provider(connection)
    const signer = provider.getSigner()
 
    const priceFormatted = ethers.utils.parseUnits(formInput.price, 'ether')
    let contract = new ethers.Contract(marketplaceAddress, NFTMarketplace.abi,
signer)
    let listingPrice = await contract.getListingPrice()
 
    listingPrice = listingPrice.toString()
    let transaction = await contract.resellToken(id, priceFormatted, { value:
listingPrice })

42
IE102.M21- Các công nghệ nền | Nhóm 4

    await transaction.wait()
 
    router.push('/')
  }
 
  return (
    <div className="flex justify-center">
      <div className="w-1/2 flex flex-col pb-12">
        <input
          placeholder="Asset Price in Eth"
          className="mt-2 border rounded p-4"
          onChange={e => updateFormInput({ ...formInput, price: e.target.value })}
        />
        {
          image && (
            <img className="rounded mt-4" width="350" src={image} />
          )
        }
        <button onClick={listNFTForSale} className="font-bold mt-4 bg-pink-500
text-white rounded p-4 shadow-lg">
          List NFT
        </button>
      </div>
    </div>
  )
}
5.7 Màn hình Dashboard
5.7.1 Màn hình và chức năng:
● Màn hình

43
IE102.M21- Các công nghệ nền | Nhóm 4

Hình 5.6 Màn hình trang Dashboard.


● Chức năng:

STT Thành phần Mô tả

1 Items Listed Liệt kê các NFT hiện có gồm thông tin về tên, mô tả, hình
ảnh, giá.

2 Thanh Tìm NFT trong danh sách hiện có


Search

3 Nút Detail Xem thông tin chi tiết NFT

Bảng 5.6 Bảng chức năng trang Dashboard.


5.7.2 Code:
/* pages/dashboard.js */
import { ethers } from 'ethers'
import { useEffect, useState } from 'react'
import axios from 'axios'
import Web3Modal from 'web3modal'

44
IE102.M21- Các công nghệ nền | Nhóm 4

import { useRouter } from 'next/router'


 
import {
  marketplaceAddress
} from '../config'
 
import NFTMarketplace from
'../artifacts/contracts/NFTMarketplace.sol/NFTMarketplace.json'
 
export default function CreatorDashboard() {
  const [searchTerm, setSearchTerm] = useState("");
  const [nfts, setNfts] = useState([])
  const [loadingState, setLoadingState] = useState('not-loaded')
  useEffect(() => {
    loadNFTs()
  }, [])
  const router = useRouter()
  async function loadNFTs() {
    const web3Modal = new Web3Modal({
      network: 'mainnet',
      cacheProvider: true,
    })
    const connection = await web3Modal.connect()
    const provider = new ethers.providers.Web3Provider(connection)
    const signer = provider.getSigner()
 
    const contract = new ethers.Contract(marketplaceAddress, NFTMarketplace.abi,
signer)
    const data = await contract.fetchItemsListed()
 
    const items = await Promise.all(data.map(async i => {
      const tokenUri = await contract.tokenURI(i.tokenId)
      const meta = await axios.get(tokenUri)
      let price = ethers.utils.formatUnits(i.price.toString(), 'ether')
      let item = {
        price,
        tokenId: i.tokenId.toNumber(),
        seller: i.seller,

45
IE102.M21- Các công nghệ nền | Nhóm 4

        owner: i.owner,
        image: meta.data.image,
        name: meta.data.name,
        description: meta.data.description,
        tokenURI : tokenUri
      }
      return item
    }))
 
    setNfts(items)
    setLoadingState('loaded')
  }
  /**
   * Chi tiết NFT
   *
   * @param {nft} nft
   */
  function detailNFT(nft) {
    router.push(`/detail-nft?id=${nft.tokenId}&tokenURI=$
{nft.tokenURI}&prev=dashboard`)
  }
  if (loadingState === 'loaded' && !nfts.length) return (<h1 className="py-10 px-
20 text-3xl">No NFTs listed</h1>)
  return (
    <div>
    <div class="w-full m-3 max-w-xs border-solid border-2 border-gray">
     <input class="shadow appearance-none border rounded w-full py-2 px-3 text-
gray-700 leading-tight focus:outline-none focus:shadow-outline" type="search"
placeholder="Search..."
      onChange={(event) => {
        setSearchTerm(event.target.value)
      } } />
    </div>
    <div>
      <div className="p-4" style={{ maxWidth: '1600px' }}>
        <h2 className="text-2xl py-2">Items Listed</h2>
          <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 pt-4">
          {

46
IE102.M21- Các công nghệ nền | Nhóm 4

            nfts.filter(nft =>
nft.name.toLowerCase().includes(searchTerm.toLowerCase())).map((nft, i) => (
              <div key={i} className="border shadow rounded-xl overflow-hidden">
                <img src={nft.image} className="rounded" />
                <div className="p-4">
                  <p style={{ height: '64px' }} className="text-2xl font-
semibold">{nft.name}</p>
                  <div style={{ height: '70px', overflow: 'hidden' }}>
                    <p className="text-gray-400">{nft.description}</p>
                  </div>
                </div>
                <div className="p-4 bg-black">
                <p className="text-2xl font-bold text-white">Price - {nft.price} Eth</p>
                  <button className="mt-4 w-full bg-pink-500 text-white font-bold py-2
px-12 rounded" onClick={() => detailNFT(nft)}>Detail</button>
                </div>
              </div>
            ))
          }
        </div>
      </div>
    </div>
    </div>
  )
}
5.8 Thanh Navbar (_app.js)
5.8.1 Hình ảnh và chức năng
● Hình ảnh:

Hình 5.7 Hình thanh Navbar.


● Chức năng:

STT Thành phần Mô tả

1 Infinity Tên của trang


Marketplace

2 Home Dẫn tới trang chủ

47
IE102.M21- Các công nghệ nền | Nhóm 4

3 Sell NFT Dẫn tới trang nhập thông tin của NFT để đăng bán

4 My NFTs Dẫn tới trang hiển thị danh sách các NFT mà tài khoản
đang sở hữu

5 Dashboard Dẫn tới trang thông tin các NFT hiện có

6 Account Hiển thị địa chỉ tài khoản đang đăng nhập và số dư hiện

Bảng 5.7 Bảng chức năng thanh Navbar.
5.8.2 Code:
/* pages/_app.js */
import "../styles/globals.css";
import Link from "next/link";
import ConnectButton from "./login/ConnectButton";
import { ChakraProvider, useDisclosure } from "@chakra-ui/react";
import { DAppProvider } from "@usedapp/core";
import AccountModal from "./login/AccountModal";
 
function MyApp({ Component, pageProps }) {
  const { isOpen, onOpen, onClose } = useDisclosure();
  return (
    <div>
      <nav className="border-b p-6">
        <div className="flex items-center justify-between">
          <div>
            <p className="font-bold text-3xl">Infinity Marketplace</p>
          </div>
          <div className="flex gap-10">
            <Link href="/">
              <a className="text-xl font-medium">🛒 Home</a>
            </Link>
            <Link href="/create-nft">
              <a className="text-xl font-medium">💸 Sell NFT</a>
            </Link>
            <Link href="/my-nfts">

48
IE102.M21- Các công nghệ nền | Nhóm 4

              <a className="text-xl font-medium">🖼️ My NFTs</a>


            </Link>
            <Link href="/dashboard">
              <a className="text-xl font-medium">📑 Dashboard</a>
            </Link>
          </div>
          <div>
            <DAppProvider config={{}}>
              <ChakraProvider>
                <ConnectButton handleOpenModal={onOpen} />
                <AccountModal isOpen={isOpen} onClose={onClose} />
              </ChakraProvider>
            </DAppProvider>
          </div>
        </div>
      </nav>
 
      <Component {...pageProps} />
    </div>
  );
}
 
export default MyApp;
5.9 Login
5.9.1 Hình ảnh và chức năng
● Hình ảnh:

49
IE102.M21- Các công nghệ nền | Nhóm 4

Hình 5.9 Hình tài khoản.


● Chức năng: Hiển thị địa chỉ ví metamask cũng như số coin mà ví đang có (lấy 3
chữ số sau phần thập phân). 
5.9.2 Code
● ConnectButton.tsx: Đây là phần code giúp hiển thị lượng coin và địa chỉ của tài
khoản trên ví metamask. 
import { Button, Box, Text } from "@chakra-ui/react";
import { useEthers, useEtherBalance } from "@usedapp/core";
import { formatEther } from "@ethersproject/units";
import Identicon from "./Identicon";
 
type Props = {
  handleOpenModal: any;
};
 
export default function ConnectButton({ handleOpenModal }: Props) {
  const { activateBrowserWallet, account } = useEthers();
  const etherBalance = useEtherBalance(account);
 
  function handleConnectWallet() {
    activateBrowserWallet();
  }

50
IE102.M21- Các công nghệ nền | Nhóm 4

// Kiểm tra xem tài khoản có kết nối hay không nếu có thì hiển thị coin và địa
chỉ, không thì hiển thị dòng chữ “connect to a wallet”
  return account ? (
    <Box
      display="flex"
      alignItems="center"
      background="gray.700"
      borderRadius="xl"
      py="0"
    >
      <Box px="3">
        <Text color="white" fontSize="md">
// Hiển thị lượng coin và format lấy sau phần thập phân 3 chữ số
          {etherBalance && parseFloat(formatEther(etherBalance)).toFixed(3)} ETH
        </Text>
      </Box>
      <Button
        onClick={handleOpenModal}
        bg="gray.800"
        border="1px solid transparent"
        _hover={{
          border: "1px",
          borderStyle: "solid",
          borderColor: "blue.400",
          backgroundColor: "gray.700",
        }}
        borderRadius="xl"
        m="1px"
        px={3}
        height="38px"
      >
        <Text color="white" fontSize="md" fontWeight="medium" mr="2">
// Hiển thị địa chỉ tài khoản 
          {account &&
            `${account.slice(0, 6)}...${account.slice(
              account.length - 4,
              account.length
            )}`}

51
IE102.M21- Các công nghệ nền | Nhóm 4

        </Text>
        <Identicon />
      </Button>
    </Box>
  ) : (
// Button khi chưa kết nối tài khoản
    <Button
      onClick={handleConnectWallet}
      bg="blue.800"
      color="blue.300"
      fontSize="lg"
      fontWeight="medium"
      borderRadius="xl"
      border="1px solid transparent"
      _hover={{
        borderColor: "blue.700",
        color: "blue.400",
      }}
      _active={{
        backgroundColor: "blue.800",
        borderColor: "blue.700",
      }}
    >
      Connect to a wallet
    </Button>
  );
}
● Identicon.tsx: Phần code này sẽ giúp hiển thị avatar ở cạnh địa chỉ tài khoản ví
import { useEffect, useRef } from "react";
import { useEthers } from "@usedapp/core";
//cần import thư viện Jazzicon để lấy đường kính tính bằng pixel trả về hình đại
diện lập thể
import Jazzicon from "@metamask/jazzicon";
import styled from "@emotion/styled";
 
const StyledIdenticon = styled.div`
  height: 1rem;
  width: 1rem;

52
IE102.M21- Các công nghệ nền | Nhóm 4

  border-radius: 1.125rem;
  background-color: black;
`;
 
export default function Identicon() {
  const ref = useRef<HTMLDivElement>();
  const { account } = useEthers();
 
  useEffect(() => {
    if (account && ref.current) {
      ref.current.innerHTML = "";
      ref.current.appendChild(Jazzicon(16, parseInt(account.slice(2, 10), 16)));
    }
  }, [account]);
 
  return <StyledIdenticon ref={ref as any} />;
}
● AccountModal.tsx: giúp hiển thị phần pop-up cung cấp thêm thông tin về tài
khoản cũng như đăng xuất kết nối với ví. 
// Phần này chủ yếu lấy các modal có sẵn trong thư viện @chakra-ui/react
import {
  Box,
  Button,
  Flex,
  Link,
  Modal,
  ModalOverlay,
  ModalContent,
  ModalHeader,
  ModalFooter,
  ModalBody,
  ModalCloseButton,
  Text,
} from "@chakra-ui/react";
import { ExternalLinkIcon, CopyIcon } from "@chakra-ui/icons";
import { useEthers } from "@usedapp/core";
import Identicon from "./Identicon";
 

53
IE102.M21- Các công nghệ nền | Nhóm 4

type Props = {
  isOpen: any;
  onClose: any;
};
 
export default function AccountModal({ isOpen, onClose }: Props) {
  const { account, deactivate } = useEthers();
 
  function handleDeactivateAccount() {
    deactivate();
    onClose();
  }
 
  return (
    <Modal isOpen={isOpen} onClose={onClose} isCentered size="md">
      <ModalOverlay />
      <ModalContent
        background="gray.900"
        border="1px"
        borderStyle="solid"
        borderColor="gray.700"
        borderRadius="3xl"
      >
        <ModalHeader color="white" px={4} fontSize="lg" fontWeight="medium">
          Account
        </ModalHeader>
        <ModalCloseButton
          color="white"
          fontSize="sm"
          _hover={{
            color: "whiteAlpha.700",
          }}
        />
        <ModalBody pt={0} px={4}>
          <Box
            borderRadius="3xl"
            border="1px"
            borderStyle="solid"

54
IE102.M21- Các công nghệ nền | Nhóm 4

            borderColor="gray.600"
            px={5}
            pt={4}
            pb={2}
            mb={3}
          >
            <Flex justifyContent="space-between" alignItems="center" mb={3}>
              <Text color="gray.400" fontSize="sm">
                Connected with MetaMask
              </Text>
              <Button
                variant="outline"
                size="sm"
                borderColor="blue.800"
                borderRadius="3xl"
                color="blue.500"
                fontSize="13px"
                fontWeight="normal"
                px={2}
                height="26px"
                _hover={{
                  background: "none",
                  borderColor: "blue.300",
                  textDecoration: "underline",
                }}
                onClick={handleDeactivateAccount}
              >
                Change
              </Button>
            </Flex>
            <Flex alignItems="center" mt={2} mb={4} lineHeight={1}>
              <Identicon />
              <Text
                color="white"
                fontSize="xl"
                fontWeight="semibold"
                ml="2"
                lineHeight="1.1"

55
IE102.M21- Các công nghệ nền | Nhóm 4

              >
                {account &&
                  `${account.slice(0, 6)}...${account.slice(
                    account.length - 4,
                    account.length
                  )}`}
              </Text>
            </Flex>
            <Flex alignContent="center" m={3}>
              <Button
                variant="link"
                color="gray.400"
                fontWeight="normal"
                fontSize="sm"
                _hover={{
                  textDecoration: "none",
                  color: "whiteAlpha.800",
                }}
              >
                <CopyIcon mr={1} />
                Copy Address
              </Button>
              <Link
                fontSize="sm"
                display="flex"
                alignItems="center"
                href={`https://ropsten.etherscan.io/address/${account}`}
                isExternal
                color="gray.400"
                ml={6}
                _hover={{
                  color: "whiteAlpha.800",
                  textDecoration: "underline",
                }}
              >
                <ExternalLinkIcon mr={1} />
                View on Explorer
              </Link>

56
IE102.M21- Các công nghệ nền | Nhóm 4

            </Flex>
          </Box>
        </ModalBody>
 
        <ModalFooter
          justifyContent="end"
          background="gray.700"
          borderBottomLeftRadius="3xl"
          borderBottomRightRadius="3xl"
          p={6}
        >
          <Text
            color="white"
            textAlign="left"
            fontWeight="medium"
            fontSize="md"
          >
            Your transactions willl appear here...
          </Text>
        </ModalFooter>
      </ModalContent>
    </Modal>
  );
}
5. 10 Thiết kế css
● Home.module.css
.container {
  padding: 0 2rem;
}
 
.main {
  min-height: 100vh;
  padding: 4rem 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

57
IE102.M21- Các công nghệ nền | Nhóm 4

}
 
.footer {
  display: flex;
  flex: 1;
  padding: 2rem 0;
  border-top: 1px solid #eaeaea;
  justify-content: center;
  align-items: center;
}
 
.footer a {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-grow: 1;
}
 
.title a {
  color: #0070f3;
  text-decoration: none;
}
 
.title a:hover,
.title a:focus,
.title a:active {
  text-decoration: underline;
}
 
.title {
  margin: 0;
  line-height: 1.15;
  font-size: 4rem;
}
 
.title,
.description {
  text-align: center;

58
IE102.M21- Các công nghệ nền | Nhóm 4

}
 
.description {
  margin: 4rem 0;
  line-height: 1.5;
  font-size: 1.5rem;
}
 
.code {
  background: #fafafa;
  border-radius: 5px;
  padding: 0.75rem;
  font-size: 1.1rem;
  font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans
Mono,
    Bitstream Vera Sans Mono, Courier New, monospace;
}
 
.grid {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  max-width: 800px;
}
 
.card {
  margin: 1rem;
  padding: 1.5rem;
  text-align: left;
  color: inherit;
  text-decoration: none;
  border: 1px solid #eaeaea;
  border-radius: 10px;
  transition: color 0.15s ease, border-color 0.15s ease;
  max-width: 300px;
}
 

59
IE102.M21- Các công nghệ nền | Nhóm 4

.card:hover,
.card:focus,
.card:active {
  color: #0070f3;
  border-color: #0070f3;
}
 
.card h2 {
  margin: 0 0 1rem 0;
  font-size: 1.5rem;
}
 
.card p {
  margin: 0;
  font-size: 1.25rem;
  line-height: 1.5;
}
 
.logo {
  height: 1em;
  margin-left: 0.5rem;
}
 
@media (max-width: 600px) {
  .grid {
    width: 100%;
    flex-direction: column;
  }
}
5. 11 API
Kết nối API với infura tới Polygon network
/* hardhat.config.js */
require("@nomiclabs/hardhat-waffle");
 
const fs = require("fs");
const privateKey = fs.readFileSync(".secret").toString();
const projectId = "88ae0db50107404793a217cdb3e29de4";
module.exports = {

60
IE102.M21- Các công nghệ nền | Nhóm 4

  defaultNetwork: "hardhat",
  networks: {
    hardhat: {
      chainId: 1337,
    },
    mumbai: {
      url: `https://polygon-mumbai.infura.io/v3/${projectId}`,
      accounts: [privateKey],
    },
  },
  solidity: {
    version: "0.8.4",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
};

61
IE102.M21- Các công nghệ nền | Nhóm 4

 CHƯƠNG 6: CÀI ĐẶT VÀ THỬ NGHIỆM


STT Chức năng Mức độ hoàn thành(%) Ghi chú

1 Đăng ký 70%

2 Tìm kiếm 85%

3 Mua bán NFT 87%


Bảng 6.1 Bảng đánh giá chức năng trang web.

62
IE102.M21- Các công nghệ nền | Nhóm 4

CHƯƠNG 6: NHẬN XÉT VÀ KẾT LUẬN


6.1 Kết quả đạt được
● Nắm được các nội dung chính về blockchain, Solidity, Smart contract, cách tạo
token và xây dựng frontend cho DAPP.
● Áp dụng được những kiến thức đã học vào trong quá trình học và thực hiện đồ
án.
● Nắm được đặc điểm và cách thức để tạo ra một NFT Marketplace. 

6.2 Ưu điểm
● Ứng dụng tạo ra có các chức năng cơ bản của một NFT Marketplace như đăng
ký, tạo, mua, bán NFT và tìm kiếm.
● Ứng dụng có giao diện dễ nhìn, đơn giản, dễ sử dụng.
● Có liên kết với metamask để người dùng dễ thanh toán.

6.3 Nhược điểm


● Các chức năng chỉ đáp ứng ở mức cơ bản, chưa có chức năng mới, nổi bật so với
các NFT Marketplace khác.
● Giao diện còn đơn giản, chưa thu hút.
● Chức năng còn hạn chế, chưa phục vụ đầy đủ nhu cầu đa dạng của người dùng.
● Chưa áp dụng cho các thiết bị mobile.

6.4 Hướng phát triển


Nhóm sẽ tiếp tục phát triển hoàn thiện ứng dụng đồng thời tìm hiểu và bổ sung
những tính năng mới. Bên cạnh đó, nhóm cũng sẽ tập trung thiết kế giao diện để
ứng dụng sinh động và bắt mắt hơn.

63
IE102.M21- Các công nghệ nền | Nhóm 4

TÀI LIỆU THAM KHẢO

1. Dabit, N. (2021, 3 12). DEV. Retrieved from How to Build a Full Stack NFT
Marketplace - V2 (2022): https://dev.to/edge-and-node/building-scalable-
full-stack-apps-on-ethereum-with-polygon-2cfb?
fbclid=IwAR2GrMBXHtiuSwSRthMegiJ61CZhIySVRQIzsLFKju76KDRR
64BWmCZnGsQ
2. Dawson, J. E. (2021, 7 19). DEV. Retrieved from Build a Web3 Dapp in
React & Login with MetaMask: https://dev.to/jacobedawson/build-a-web3-
dapp-in-react-login-with-metamask-4chp
3. Hun, P. (2021, 10 4). GPS Blockchain. Retrieved from Polygon (MATIC) là
gì? Tổng quan về dự án MATIC: https://gfsblockchain.com/tong-quan-ve-
du-an-matic-polygon.html
4. ITNavi. (2022, 1 13). ITNavi. Retrieved from Solidity là gì? Tổng quan về
ngôn ngữ lập trình Solidity: https://itnavi.com.vn/blog/solidity-la-gi
5. Niềm Vui Lập Trình . (2021, 9 6). Retrieved from Tailwind CSS Dành Cho
Bạn Mới Bắt Đầu 2021: https://www.niemvuilaptrinh.com/article/tailwind-
css-danh-cho-ban-moi-bat-dau-2021

64
IE102.M21- Các công nghệ nền | Nhóm 4

PHÂN CHIA CÔNG VIỆC

STT MSSV Công việc được giao Mức độ hoàn


thành(%)

1 18520063 Làm tính năng đăng ký và chi tiết NFT 92%

2 18521682 Làm mục Cơ sở lý thuyết 80%


Làm mục phân tích trang web

3 18521692 Làm tính năng Chi tiết NFT, quay demo 91%

4 19521403 Làm mục Tổng quan và Nhận xét và kết 80%


luận
Format word

5 19521558 Phân chia công việc 90%


Làm tính năng tìm kiếm
Làm mục Tổng Quan và Thiết kế hệ
thống
Làm slide và thuyết trình

6 19521733 Làm tính năng tìm kiếm 91%


Làm mục Cài đặt môi trường và Phân tích
trang web

7 19521746 Làm chức năng đăng ký, thuyết trình 93%

65

You might also like