DApp开发 阶段三 ✅ 验证通过
CID = 内容的身份证,由哈希算法+编码方式+数据哈希组成。
// CID结构解析
CIDv0: QmXxYz... ← 直接是SHA-256的Base58编码
CIDv1: bafybeigdyr... ← 更灵活的格式
CIDv1详细结构:
┌────────┬──────────┬──────────┬────────────────────┐
│ 版本 │ 编码方式 │ 哈希算法 │ 哈希值 │
│ 0x01 │ 0x50(多) │ 0x12(多) │ SHA-256结果 │
│ CIDv1 │ dag-json │ sha2-256 │ 32字节 │
└────────┴──────────┴──────────┴────────────────────┘
// 示例:NFT元数据的CID
ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylq2f6zeg3rjnl3kcqyy/
// 在浏览器中访问
https://ipfs.io/ipfs/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylq2f6zeg3rjnl3kcqyy/
https://dweb.link/ipfs/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylq2f6zeg3rjnl3kcqyy/
// contracts/IPFSRegistry.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/**
* @title IPFSRegistry
* @dev 去中心化数据注册表——链上存CID,链下存内容
*/
contract IPFSRegistry {
address public owner;
// 每条记录的CID(bytes32可以存CIDv0或截断的CIDv1)
struct Record {
string cid; // 完整IPFS CID
string name; // 记录名称
address creator; // 创建者
uint256 timestamp; // 创建时间
uint256 size; // 文件大小(字节)
}
mapping(uint256 => Record) public records;
uint256 public recordCount;
event RecordAdded(uint256 indexed id, string cid, address creator);
constructor() {
owner = msg.sender;
}
/// @notice 注册一条IPFS记录
function addRecord(
string memory _cid,
string memory _name,
uint256 _size
) external returns (uint256) {
require(bytes(_cid).length > 0, "CID cannot be empty");
uint256 id = recordCount;
records[id] = Record({
cid: _cid,
name: _name,
creator: msg.sender,
timestamp: block.timestamp,
size: _size
});
emit RecordAdded(id, _cid, msg.sender);
recordCount++;
return id;
}
/// @notice 获取记录的完整IPFS URL
function getIPFSUrl(uint256 _id) external view returns (string memory) {
require(_id < recordCount, "Record not found");
return string(abi.encodePacked("ipfs://", records[_id].cid));
}
/// @notice 获取记录的HTTP网关URL
function getGatewayUrl(uint256 _id) external view returns (string memory) {
require(_id < recordCount, "Record not found");
return string(abi.encodePacked("https://ipfs.io/ipfs/", records[_id].cid));
}
}
// contracts/IPFSNFT.sol — 基于IPFS存储的NFT
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
contract IPFSNFT is ERC721 {
using Strings for uint256;
uint256 public nextTokenId;
string private _baseURI; // IPFS CID作为baseURI
address public owner;
constructor(string memory _cid) ERC721("IPFS Art", "IART") {
owner = msg.sender;
// 设置IPFS基础URI
_baseURI = string(abi.encodePacked("ipfs://", _cid, "/"));
}
function mint() external {
_safeMint(msg.sender, nextTokenId);
nextTokenId++;
}
// 返回每个token的元数据URI
// 例如:ipfs://QmXXX/0.json
function tokenURI(uint256 tokenId)
public view override
returns (string memory)
{
_requireOwned(tokenId);
return string(abi.encodePacked(_baseURI, tokenId.toString(), ".json"));
}
// 更新基础URI(用于元数据迁移)
function setBaseURI(string memory _cid) external {
require(msg.sender == owner, "Not owner");
_baseURI = string(abi.encodePacked("ipfs://", _cid, "/"));
}
}
// 存储在IPFS上的元数据文件: 0.json
{
"name": "IPFS Art #0",
"description": "A fully decentralized NFT with IPFS metadata",
"image": "ipfs://QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG/image.png",
"attributes": [
{
"trait_type": "Rarity",
"value": "Legendary"
},
{
"trait_type": "Power",
"value": 95
},
{
"display_type": "date",
"trait_type": "Created",
"value": 1700000000
}
]
}
// scripts/upload-to-ipfs.js
const axios = require("axios");
const fs = require("fs");
const FormData = require("form-data");
const PINATA_API_KEY = process.env.PINATA_API_KEY;
const PINATA_SECRET = process.env.PINATA_SECRET;
// 上传单个文件到IPFS
async function uploadFile(filePath) {
const formData = new FormData();
formData.append("file", fs.createReadStream(filePath));
const response = await axios.post(
"https://api.pinata.cloud/pinning/pinFileToIPFS",
formData,
{
headers: {
...formData.getHeaders(),
pinata_api_key: PINATA_API_KEY,
pinata_secret_api_key: PINATA_SECRET,
},
}
);
console.log("上传成功!CID:", response.data.IpfsHash);
console.log("IPFS URL:", `ipfs://${response.data.IpfsHash}`);
return response.data.IpfsHash;
}
// 上传JSON元数据到IPFS
async function uploadMetadata(metadata) {
const response = await axios.post(
"https://api.pinata.cloud/pinning/pinJSONToIPFS",
{ pinataContent: metadata },
{
headers: {
pinata_api_key: PINATA_API_KEY,
pinata_secret_api_key: PINATA_SECRET,
},
}
);
console.log("元数据CID:", response.data.IpfsHash);
return response.data.IpfsHash;
}
// 批量上传NFT元数据
async function uploadNFTCollection(count) {
const cids = [];
for (let i = 0; i < count; i++) {
const metadata = {
name: `IPFS Art #${i}`,
description: "Decentralized NFT collection",
image: `ipfs://QmImageCID/${i}.png`,
attributes: [
{ trait_type: "ID", value: i },
{ trait_type: "Rarity", value: i % 10 === 0 ? "Legendary" : "Common" },
],
};
const cid = await uploadMetadata(metadata);
cids.push({ id: i, cid });
console.log(`Token #${i} → CID: ${cid}`);
}
return cids;
}
// 使用示例
uploadNFTCollection(10).then(console.log);
// scripts/nft-storage-upload.js
const { NFTStorage, Blob } = require("nft.storage");
const client = new NFTStorage({ token: process.env.NFT_STORAGE_TOKEN });
async function storeNFT(imagePath, name, description) {
const imageData = fs.readFileSync(imagePath);
// nft.storage会自动创建ERC-721兼容的元数据
const metadata = await client.store({
name,
description,
image: new Blob([imageData], { type: "image/png" }),
});
console.log("元数据CID:", metadata.ipnft);
console.log("元数据URL:", metadata.url);
// 元数据URL格式: ipfs://bafyreiXXX/metadata.json
return metadata;
}
ipfs pin add CID — 自己的节点固定1. 为什么不在智能合约中直接存储图片/大文件?
2. CID的核心特性是什么?
3. 为什么IPFS数据需要Pinning?
| IPFS | Arweave | |
|---|---|---|
| 共识 | 无(需Pin服务) | Proof of Access |
| 存储 | 临时(可能被GC) | 永久 |
| 付费 | Pin服务月费 | 一次付费永久 |
| 速度 | 快(多节点并行) | 较慢 |
| 适用 | NFT元数据/网站 | 永久存档 |
| 代表 | Pinata/nft.storage | ArDrive |
// CID生成过程
// 1. 文件内容 → SHA-256哈希
// 2. 添加多编解码器前缀(multicodec)
// 3. 添加多哈希前缀(multihash)
// 4. Base32编码 → CIDv1 (bafy...)
// 前端验证CID完整性
async function verifyCID(content, expectedCid) {
const hash = await crypto.subtle.digest('SHA-256', content);
const computedCid = encodeCID(hash);
return computedCid === expectedCid;
}
// IPFS DAG: 目录结构
// ipfs://QmDir/ → 目录CID
// ├── image.png → QmImg
// └── metadata.json → QmMeta
你已掌握IPFS去中心化存储!从内容寻址原理到NFT元数据,从Pinata上传到Pinning策略,你打通了链上链下的数据桥梁。
关键收获:
✅ IPFS内容寻址与CID原理
✅ 链上哈希+链下数据的架构模式
✅ NFT元数据JSON格式与IPFS存储
✅ Pinata/nft.storage上传实战
✅ Pinning与数据持久化策略