Professional Documents
Culture Documents
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
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.
4
IE102.M21- Các công nghệ nền | Nhóm 4
5
IE102.M21- Các công nghệ nền | Nhóm 4
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
7
IE102.M21- Các công nghệ nền | Nhóm 4
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
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.
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.
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
15
IE102.M21- Các công nghệ nền | Nhóm 4
(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
18
IE102.M21- Các công nghệ nền | Nhóm 4
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
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
23
IE102.M21- Các công nghệ nền | Nhóm 4
24
IE102.M21- Các công nghệ nền | Nhóm 4
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
27
IE102.M21- Các công nghệ nền | Nhóm 4
</div>
);
28
IE102.M21- Các công nghệ nền | Nhóm 4
29
IE102.M21- Các công nghệ nền | Nhóm 4
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
3 Nút List Chuyển đến trang Resell NFT để thực hiện bán lại NFT đang
sở hữu
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
35
IE102.M21- Các công nghệ nền | Nhóm 4
2 Description Mô tả NFT
3 Token ID Mã token
36
IE102.M21- Các công nghệ nền | Nhóm 4
37
IE102.M21- Các công nghệ nền | Nhóm 4
38
IE102.M21- Các công nghệ nền | Nhóm 4
39
IE102.M21- Các công nghệ nền | Nhóm 4
40
IE102.M21- Các công nghệ nền | Nhóm 4
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
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
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á.
44
IE102.M21- Các công nghệ nền | Nhóm 4
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:
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
6 Account Hiển thị địa chỉ tài khoản đang đăng nhập và số dư hiện
có
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
49
IE102.M21- Các công nghệ nền | Nhóm 4
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
1 Đăng ký 70%
62
IE102.M21- Các công nghệ nền | Nhóm 4
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.
63
IE102.M21- Các công nghệ nền | Nhóm 4
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
3 18521692 Làm tính năng Chi tiết NFT, quay demo 91%
65