IPFS là gì? 🤔
Theo Wikipedia, Hệ thống Tệp Liên Hành Tinh (InterPlanetary File System - IPFS) là một giao thức phi tập trung, siêu phương tiện và mạng ngang hàng dùng để lưu trữ và chia sẻ tệp một cách phân tán. Bằng cách sử dụng phương pháp định địa chỉ nội dung (content-addressing), IPFS xác định duy nhất các tệp trong một không gian tên toàn cầu kết nối các máy chủ IPFS, tạo thành một hệ thống siêu phương tiện cho phép phân phối dữ liệu hiệu quả và đáng tin cậy.
Tính năng tương tác với các ứng dụng phi tập trung là một điểm mạnh của IPFS, cung cấp một nền tảng vững chắc cho hệ sinh thái Web3 và blockchain.
Trong bài viết này, chúng ta sẽ phát triển một ứng dụng phi tập trung trên mạng thử nghiệm Morph Holesky, sử dụng Pinata làm hệ thống lưu trữ để tạo và lưu trữ IPFS Hash trên chuỗi.
Yêu cầu cơ bản 📚
NodeJs (Phiên bản 18 hoặc mới hơn)
NPM (Phiên bản 7 hoặc mới hơn)
Ví Metamask
Ether trên mạng thử nghiệm
Pinata API Key
Công cụ Dev 🛠
Yarn
npm install -g yarn
Bước 1: Tạo một ứng dụng React mới
Hãy bắt đầu bằng việc tạo một ứng dụng React mới có tên là ipfs-hash-storage:
npm create vite@latest ipfs-hash-storage --template react
✍ Làm theo hướng dẫn bằng cách chọn React và JavaScript.
Điều hướng đến thư mục của ứng dụng React vừa tạo:
cd ipfs-hash-storage
Bước 2: Cài đặt gói Hardhat làm thư viện phụ thuộc
yarn add hardhat
Bước 3: Khởi tạo Hardhat làm môi trường phát triển Ethereum
npx hardhat init
✍ Làm theo hướng dẫn bằng cách chọn Tạo một dự án JavaScript.
Xóa các tệp trong thư mục contracts
và test
để bắt đầu một dự án mới từ đầu.
Phần thưởng: 💚 Cách tạo Pinata API Key
Đăng ký hoặc đăng nhập tại pinata.cloud.
Trong thanh bên, chọn API Keys.
Ở góc trên bên phải, nhấp vào New Key.
Chọn Admin để truy cập tất cả các endpoint và cài đặt tài khoản.
Đặt tên cho khóa và nhấp vào Generate Key.
✍ Một cửa sổ sẽ hiện ra chứa toàn bộ thông tin của API Key.
Sao chép JWT Token và lưu trữ cẩn thận.
Trong thanh bên, chọn Gateways và sao chép Domain link, sau đó lưu trữ cẩn thận.
✍ Domain link này sẽ được sử dụng để tương tác với node IPFS thông qua Pinata.
Bước 4: Thiết lập thông tin môi trường
Cài đặt plugin để cấu hình biến môi trường:
yarn add --dev dotenv
Tạo một tệp mới có tên là .env
trong thư mục gốc và thêm các biến sau:
RPC_URL="https://rpc-quicknode-holesky.morphl2.io"
DEV_PRIVATE_KEY="insert-your-private-key-here"
VITE_PINATA_JWT="insert-your-jwt-token-here"
VITE_PINATA_GATEWAY="insert-your-gateway-domain-link-here"
Bước 5: Cấu hình Hardhat để phát triển DApp
Mở tệp hardhat.config.cjs
và cấu hình như sau:
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
const { RPC_URL, DEV_PRIVATE_KEY } = process.env;
module.exports = {
solidity: "0.8.25",
paths: {
artifacts: "./src/artifacts",
},
networks: {
morphTestnet: {
chainId: 2810,
url: RPC_URL,
accounts: [DEV_PRIVATE_KEY],
gasPrice: 2000000000,
},
},
};
Bước 6: Cấu hình Pinata cho mục đích lưu trữ
Cài đặt plugin Pinata:
yarn add pinata
Đi đến thư mục src
và tạo tệp mới có tên là config.js
.
Thêm mã sau vào tệp src/config.js
để cấu hình Pinata:
import { PinataSDK } from "pinata";
const pinataJwt = import.meta.env.VITE_PINATA_JWT;
const pinataGateway = import.meta.env.VITE_PINATA_GATEWAY;
export const pinata = new PinataSDK({
pinataJwt,
pinataGateway,
});
Bước 7: Viết mã Solidity cho hợp đồng thông minh
Trong thư mục contracts
, tạo tệp mới có tên là IpfsHashStorage.sol
.
Dưới đây là mã nguồn cho hợp đồng thông minh:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
contract IpfsHashStorage {
string private ipfsHash;
// Hàm thiết lập Hash
function setIPFSHash (string memory _ipfsHash) public {
ipfsHash = _ipfsHash;
}
// Hàm lấy Hash
function getIPFSHash() public view returns(string memory) {
return ipfsHash;
}
}
Bước 8: Biên dịch hợp đồng thông minh
Thực hiện lệnh sau để biên dịch mã hợp đồng thông minh:
yarn hardhat compile
Kết quả biên dịch sẽ hiển thị như sau:
Compiled 1 Solidity file successfully (evm target: paris).
✨ Done in 3.70s.
Bước 9: Triển khai DApp lên mạng thử nghiệm Morph
Cài đặt gói triển khai Hardhat:
yarn add --dev hardhat-deploy
Mở tệp hardhat.config.cjs
và thêm phần cấu hình deployer như sau:
namedAccounts: {
deployer: {
default: 0,
},
},
Tạo thư mục mới deploy
ở thư mục gốc, sau đó thêm tệp deploy.cjs
với nội dung sau:
module.exports = async ({ getNamedAccounts, deployments }) => {
const { deploy, log } = deployments;
const { deployer } = await getNamedAccounts();
const args = [];
await deploy("IpfsHashStorage", {
contract: "IpfsHashStorage",
args: args,
from: deployer,
log: true,
});
};
module.exports.tags = ["IpfsHashStorage"];
Tích hợp Frontend với Blockchain
Dùng mã React để xây dựng giao diện người dùng, tải lên Pinata, lưu trữ Hash trên blockchain, và truy xuất Hash từ blockchain.
Bước 10: Cài đặt thư viện tương tác với blockchain
Để ứng dụng React tương tác với hợp đồng thông minh, chúng ta cần cài đặt một số thư viện cần thiết:
yarn add ethers web3modal
Bước 11: Tích hợp giao diện người dùng với Pinata và IPFS
Trong thư mục src
, tạo hai tệp:
UploadToPinata.js
– Dùng để tải tệp lên Pinata.IpfsHashManager.js
– Quản lý giao tiếp với hợp đồng thông minh để lưu trữ và truy xuất Hash từ blockchain.
Tệp UploadToPinata.js
Tệp này sẽ sử dụng Pinata SDK để tải tệp lên và nhận về IPFS Hash:
import { pinata } from "./config";
export const uploadToPinata = async (file) => {
try {
const formData = new FormData();
formData.append("file", file);
const response = await pinata.pinFileToIPFS(formData);
return response.IpfsHash; // Hash IPFS của tệp
} catch (error) {
console.error("Lỗi khi tải lên Pinata:", error);
throw new Error("Không thể tải lên Pinata");
}
};
Tệp IpfsHashManager.js
Tệp này sẽ kết nối với hợp đồng thông minh để lưu và lấy IPFS Hash:
import { ethers } from "ethers";
import IpfsHashStorageABI from "./artifacts/contracts/IpfsHashStorage.sol/IpfsHashStorage.json";
const contractAddress = "Địa chỉ triển khai hợp đồng thông minh";
export const setIpfsHashOnBlockchain = async (ipfsHash) => {
if (!window.ethereum) throw new Error("MetaMask chưa được cài đặt");
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const contract = new ethers.Contract(contractAddress, IpfsHashStorageABI.abi, signer);
const tx = await contract.setIPFSHash(ipfsHash);
await tx.wait();
return tx.hash; // Trả về hash giao dịch
};
export const getIpfsHashFromBlockchain = async () => {
if (!window.ethereum) throw new Error("MetaMask chưa được cài đặt");
const provider = new ethers.providers.Web3Provider(window.ethereum);
const contract = new ethers.Contract(contractAddress, IpfsHashStorageABI.abi, provider);
return await contract.getIPFSHash();
};
Bước 12: Xây dựng giao diện người dùng chính
Trong tệp src/App.js
, tạo giao diện người dùng để tải tệp, lưu Hash lên blockchain, và truy xuất lại:
import React, { useState } from "react";
import { uploadToPinata } from "./UploadToPinata";
import { setIpfsHashOnBlockchain, getIpfsHashFromBlockchain } from "./IpfsHashManager";
const App = () => {
const [selectedFile, setSelectedFile] = useState(null);
const [ipfsHash, setIpfsHash] = useState("");
const [retrievedHash, setRetrievedHash] = useState("");
const handleFileChange = (e) => {
setSelectedFile(e.target.files[0]);
};
const handleUpload = async () => {
try {
if (!selectedFile) throw new Error("Chọn tệp trước khi tải lên");
const hash = await uploadToPinata(selectedFile);
setIpfsHash(hash);
alert(`Tệp được tải lên thành công: ${hash}`);
} catch (error) {
console.error(error);
alert("Tải lên thất bại");
}
};
const handleSaveToBlockchain = async () => {
try {
if (!ipfsHash) throw new Error("Không có IPFS Hash để lưu");
const txHash = await setIpfsHashOnBlockchain(ipfsHash);
alert(`Lưu Hash lên blockchain thành công: ${txHash}`);
} catch (error) {
console.error(error);
alert("Lưu lên blockchain thất bại");
}
};
const handleRetrieveFromBlockchain = async () => {
try {
const hash = await getIpfsHashFromBlockchain();
setRetrievedHash(hash);
alert(`Hash truy xuất từ blockchain: ${hash}`);
} catch (error) {
console.error(error);
alert("Truy xuất từ blockchain thất bại");
}
};
return (
<div style={{ padding: "20px" }}>
<h1>IPFS Hash Storage DApp</h1>
<div>
<input type="file" onChange={handleFileChange} />
<button onClick={handleUpload}>Tải tệp lên Pinata</button>
</div>
<div>
<p>IPFS Hash: {ipfsHash}</p>
<button onClick={handleSaveToBlockchain}>Lưu Hash lên Blockchain</button>
</div>
<div>
<button onClick={handleRetrieveFromBlockchain}>Truy xuất Hash từ Blockchain</button>
<p>Hash từ Blockchain: {retrievedHash}</p>
</div>
</div>
);
};
export default App;
Bước 13: Chạy ứng dụng
Để khởi chạy ứng dụng React:
Ứng dụng sẽ chạy tại địa chỉ http://localhost:3000
.
Kết quả
Người dùng có thể tải tệp lên Pinata để nhận IPFS Hash.
Hash được lưu trên blockchain thông qua hợp đồng thông minh.
Hash có thể được truy xuất từ blockchain thông qua ứng dụng.
🎉 Chúc mừng, bạn đã xây dựng thành công một DApp sử dụng IPFS và blockchain!