· Nacho Coll · Guides · 14 분 소요
NFT 메타데이터 저장: NFT 제작자를 위한 IPFS 완벽 가이드
NFT 메타데이터를 IPFS에 저장하기 위한 단계별 가이드. ERC-721 tokenURI 패턴, Python 및 JavaScript 예제 포함.

NFT를 만들려면 단순한 스마트 컨트랙트 배포 이상이 필요합니다 — 메타데이터와 자산을 위한 안정적이고 분산화된 저장소가 필요합니다. 이 종합 가이드는 업계 모범 사례를 사용하여 IPFS에 NFT 메타데이터를 저장하는 방법을 Python과 JavaScript 개발자를 위한 코드 예제와 함께 단계별로 안내합니다.

왜 NFT 메타데이터 저장에 IPFS를 사용해야 하나요?
전통적인 웹 호스팅은 NFT 프로젝트에 중앙화 위험을 만듭니다. 메타데이터가 일반 서버에 있으면 호스팅 서비스가 다운되거나 URL이 변경될 경우 NFT가 “깨질” 수 있습니다. IPFS(InterPlanetary File System)는 다음을 제공함으로써 이를 해결합니다:
- 불변 콘텐츠 주소 지정: 각 파일은 절대 변하지 않는 고유한 Content Identifier(CID)를 받습니다
- 분산 저장소: 콘텐츠는 전 세계 여러 노드에 존재합니다
- 암호학적 검증: 콘텐츠 해싱을 통해 파일 무결성이 보장됩니다
- 미래 보장 URL: IPFS 링크는 무기한 작동하여 장기적 가치를 보호합니다
IPFS 기본에 대한 더 깊은 이해를 위해서는 IPFS 피닝이란 무엇인가 가이드를 확인하세요.
ERC-721 메타데이터 구조 이해하기
ERC-721 표준은 NFT 메타데이터가 어떻게 구조화되어야 하는지를 정의합니다. 스마트 컨트랙트의 tokenURI 함수는 다음 패턴을 따르는 JSON 메타데이터를 가리키는 URL을 반환합니다:
{
"name": "My Amazing NFT #1",
"description": "A unique digital artwork showcasing...",
"image": "ipfs://QmYourImageCIDHere",
"attributes": [
{
"trait_type": "Background",
"value": "Blue"
},
{
"trait_type": "Rarity",
"value": "Common"
}
],
"external_url": "https://yourproject.com/token/1"
}주요 메타데이터 필드
- name: 지갑과 마켓플레이스에 표시되는 NFT 제목
- description: NFT에 대한 자세한 정보
- image: 메인 비주얼 자산에 대한 IPFS URL
- attributes: 필터링 및 희귀도 계산을 위한 특성 기반 속성
- external_url: 추가 콘텐츠 또는 프로젝트 웹사이트에 대한 선택적 링크
단계별 IPFS NFT 저장 프로세스
1단계: 자산과 메타데이터 준비
업로드하기 전에 파일을 정리하세요:
- 메인 자산: 이미지, 비디오 또는 기타 주요 콘텐츠
- 메타데이터 파일: 각 NFT를 설명하는 JSON 파일
- 컬렉션 메타데이터: 선택적 컬렉션 수준 정보
2단계: 자산을 IPFS에 업로드
메인 NFT 자산(이미지, 비디오 등)을 업로드하여 IPFS CID를 얻는 것부터 시작합니다. 메타데이터 JSON 파일에서 이러한 CID를 참조합니다.
Python을 사용하여 이미지를 업로드하는 방법은 다음과 같습니다:
import requests
import base64
import json
def upload_image_to_ipfs(image_path, api_key):
"""Upload an image file to IPFS and return its CID"""
# Read and encode image
with open(image_path, 'rb') as f:
image_data = base64.b64encode(f.read()).decode('utf-8')
# Prepare upload payload
payload = {
"content": image_data,
"description": f"NFT Asset: {image_path}"
}
headers = {
"Content-Type": "application/json",
"X-Api-Key": api_key
}
# Upload to IPFS.NINJA
response = requests.post(
"https://api.ipfs.ninja/upload/new",
headers=headers,
json=payload
)
if response.status_code == 200:
result = response.json()
print(f"✅ Image uploaded successfully!")
print(f"CID: {result['cid']}")
print(f"IPFS URL: {result['uris']['ipfs']}")
print(f"Gateway URL: {result['uris']['url']}")
return result['cid']
else:
print(f"❌ Upload failed: {response.text}")
return None
# Example usage
API_KEY = "bws_1234567890abcdef1234567890abcdef" # Replace with your actual key
image_cid = upload_image_to_ipfs("my-nft-artwork.png", API_KEY)3단계: 메타데이터 JSON 만들기 및 업로드
자산 CID를 얻으면 메타데이터 JSON 파일을 만들고 업로드합니다:
def create_and_upload_metadata(name, description, image_cid, attributes, api_key):
"""Create NFT metadata JSON and upload to IPFS"""
# Create metadata object
metadata = {
"name": name,
"description": description,
"image": f"ipfs://{image_cid}",
"attributes": attributes
}
# Convert to JSON string and encode
metadata_json = json.dumps(metadata, indent=2)
metadata_b64 = base64.b64encode(metadata_json.encode('utf-8')).decode('utf-8')
# Upload metadata
payload = {
"content": metadata_b64,
"description": f"NFT Metadata: {name}",
"metadata": {
"contentType": "application/json",
"nftTokenId": name.split('#')[1] if '#' in name else "1"
}
}
headers = {
"Content-Type": "application/json",
"X-Api-Key": api_key
}
response = requests.post(
"https://api.ipfs.ninja/upload/new",
headers=headers,
json=payload
)
if response.status_code == 200:
result = response.json()
print(f"✅ Metadata uploaded successfully!")
print(f"Metadata CID: {result['cid']}")
return result['cid']
else:
print(f"❌ Metadata upload failed: {response.text}")
return None
# Example usage
attributes = [
{"trait_type": "Background", "value": "Cosmic Blue"},
{"trait_type": "Eyes", "value": "Laser"},
{"trait_type": "Rarity", "value": "Epic"}
]
metadata_cid = create_and_upload_metadata(
name="Cosmic Warrior #001",
description="A fierce warrior from the distant galaxies, wielding the power of stars.",
image_cid=image_cid,
attributes=attributes,
api_key=API_KEY
)4단계: JavaScript 구현
웹 애플리케이션이나 Node.js 프로젝트의 경우 JavaScript 버전은 다음과 같습니다:
class NFTStorage {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://api.ipfs.ninja';
}
async uploadFile(fileContent, description) {
const payload = {
content: fileContent, // base64 encoded
description: description
};
const response = await fetch(`${this.baseUrl}/upload/new`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': this.apiKey
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.statusText}`);
}
return await response.json();
}
async uploadImageFromFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async (e) => {
try {
const base64Content = e.target.result.split(',')[1]; // Remove data:image/...;base64, prefix
const result = await this.uploadFile(base64Content, `NFT Image: ${file.name}`);
resolve(result.cid);
} catch (error) {
reject(error);
}
};
reader.readAsDataURL(file);
});
}
async uploadMetadata(name, description, imageCid, attributes = []) {
const metadata = {
name,
description,
image: `ipfs://${imageCid}`,
attributes
};
const metadataJson = JSON.stringify(metadata, null, 2);
const base64Metadata = btoa(metadataJson);
const result = await this.uploadFile(base64Metadata, `NFT Metadata: ${name}`);
return result.cid;
}
}
// Usage example
const storage = new NFTStorage('bws_1234567890abcdef1234567890abcdef'); // Replace with your key
// Upload process
async function createNFT() {
try {
// Assuming you have a file input element
const fileInput = document.getElementById('nft-image');
const imageFile = fileInput.files[0];
console.log('Uploading image...');
const imageCid = await storage.uploadImageFromFile(imageFile);
console.log(`Image uploaded: ${imageCid}`);
console.log('Uploading metadata...');
const metadataCid = await storage.uploadMetadata(
'Galaxy Explorer #042',
'A mysterious explorer traversing the cosmic void.',
imageCid,
[
{ trait_type: 'Class', value: 'Explorer' },
{ trait_type: 'Galaxy', value: 'Andromeda' },
{ trait_type: 'Rarity', value: 'Legendary' }
]
);
console.log(`Metadata uploaded: ${metadataCid}`);
console.log(`Token URI: ipfs://${metadataCid}`);
} catch (error) {
console.error('Upload failed:', error);
}
}스마트 컨트랙트에서 tokenURI 구현하기
메타데이터가 IPFS에 업로드되면 ERC-721 컨트랙트에 tokenURI 함수를 구현합니다:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyNFTCollection is ERC721, Ownable {
mapping(uint256 => string) private _tokenURIs;
string private _baseTokenURI;
constructor(string memory name, string memory symbol) ERC721(name, symbol) {}
function setTokenURI(uint256 tokenId, string memory uri) external onlyOwner {
require(_exists(tokenId), "Token does not exist");
_tokenURIs[tokenId] = uri;
}
function setBaseURI(string memory baseURI) external onlyOwner {
_baseTokenURI = baseURI;
}
function tokenURI(uint256 tokenId) public view virtual override returns (string) {
require(_exists(tokenId), "Token does not exist");
string memory _tokenURI = _tokenURIs[tokenId];
// Return specific token URI if set
if (bytes(_tokenURI).length > 0) {
return _tokenURI;
}
// Fall back to base URI + token ID pattern
if (bytes(_baseTokenURI).length > 0) {
return string(abi.encodePacked(_baseTokenURI, tokenId.toString()));
}
return "";
}
function mintWithURI(address to, uint256 tokenId, string memory uri) external onlyOwner {
_mint(to, tokenId);
_tokenURIs[tokenId] = uri;
}
}대규모 컬렉션을 위한 배치 작업
대규모 NFT 컬렉션의 경우 배치 작업으로 시간과 가스 비용을 절약할 수 있습니다:
def batch_upload_collection(collection_data, api_key):
"""Upload an entire NFT collection in batches"""
print(f"Starting batch upload of {len(collection_data)} NFTs...")
results = []
for i, nft_data in enumerate(collection_data):
print(f"Processing NFT {i+1}/{len(collection_data)}: {nft_data['name']}")
try:
# Upload image
image_cid = upload_image_to_ipfs(nft_data['image_path'], api_key)
if image_cid:
# Upload metadata
metadata_cid = create_and_upload_metadata(
name=nft_data['name'],
description=nft_data['description'],
image_cid=image_cid,
attributes=nft_data['attributes'],
api_key=api_key
)
if metadata_cid:
results.append({
'token_id': i + 1,
'name': nft_data['name'],
'image_cid': image_cid,
'metadata_cid': metadata_cid,
'token_uri': f"ipfs://{metadata_cid}"
})
except Exception as e:
print(f"❌ Error processing {nft_data['name']}: {e}")
print(f"✅ Batch upload complete! {len(results)} NFTs processed successfully.")
return results
# Example collection data
collection_data = [
{
'name': 'Cosmic Warrior #001',
'description': 'A fierce warrior from distant galaxies.',
'image_path': 'images/warrior_001.png',
'attributes': [
{'trait_type': 'Class', 'value': 'Warrior'},
{'trait_type': 'Rarity', 'value': 'Epic'}
]
},
# Add more NFTs...
]
results = batch_upload_collection(collection_data, API_KEY)NFT 메타데이터 저장 모범 사례
1. 설명적인 파일 이름 사용
IPFS에 업로드할 때 정리에 도움이 되는 의미 있는 설명을 사용하세요:
payload = {
"content": base64_content,
"description": f"Collection: {collection_name} | Token: {token_id} | Type: {file_type}"
}2. 적절한 에러 처리 구현
업로드 실패를 항상 우아하게 처리하세요:
import time
def upload_with_retry(upload_function, max_retries=3, delay=2):
"""Upload with exponential backoff retry logic"""
for attempt in range(max_retries):
try:
return upload_function()
except Exception as e:
if attempt == max_retries - 1:
raise e
print(f"Attempt {attempt + 1} failed: {e}. Retrying in {delay} seconds...")
time.sleep(delay)
delay *= 2 # Exponential backoff3. 메타데이터 구조 검증
메타데이터가 표준을 따르는지 확인하세요:
def validate_metadata(metadata):
"""Validate NFT metadata structure"""
required_fields = ['name', 'description', 'image']
for field in required_fields:
if field not in metadata:
raise ValueError(f"Missing required field: {field}")
if not metadata['image'].startswith('ipfs://'):
raise ValueError("Image must be an IPFS URL")
if 'attributes' in metadata:
for attr in metadata['attributes']:
if 'trait_type' not in attr or 'value' not in attr:
raise ValueError("Invalid attribute structure")
return True올바른 IPFS 피닝 서비스 선택
NFT 프로젝트를 위한 IPFS 피닝 서비스를 선택할 때 다음을 고려하세요:
- 신뢰성: 장기 저장을 위한 보장된 가동 시간
- 성능: 전 세계에서 빠른 검색 속도
- 가격: 컬렉션 크기에 비용 효율적
- 기능: API 기능, 분석 및 개발자 도구
피닝 서비스의 자세한 비교는 IPFS Ninja vs Pinata 비교와 최고의 IPFS 피닝 서비스 가이드를 참조하세요.
고급 기능: 사용자 정의 게이트웨이 및 분석
IPFS Ninja는 전문 NFT 프로젝트를 위한 추가 기능을 제공합니다:
사용자 정의 게이트웨이 구성
컬렉션을 위한 브랜드 IPFS 게이트웨이를 만드세요:
// Access your NFT through a custom gateway
const customGateway = 'https://my-collection.gw.ipfs.ninja';
const nftUrl = `${customGateway}/ipfs/${metadataCid}`;업로드 분석
대시보드 분석을 통해 NFT 저장소 사용량과 액세스 패턴을 모니터링하여 컬렉션 성능을 이해하고 저장 비용을 최적화하는 데 도움이 됩니다.
일반적인 문제 해결
메타데이터가 로드되지 않음
- IPFS URL이
ipfs://프로토콜을 사용하는지 확인하세요 - 메타데이터 JSON이 유효한지 확인하세요
- 피닝 서비스가 콘텐츠를 유지하고 있는지 확인하세요
이미지가 표시되지 않음
- 메타데이터에서 이미지 CID가 올바른지 확인하세요
- IPFS 게이트웨이에서 이미지 URL을 테스트하세요
- 이미지 파일 형식이 웹 호환되는지 확인하세요
가스 추정 오류
tokenURI함수가 유효한 문자열을 반환하는지 확인하세요- 메타데이터의 순환 참조를 확인하세요
- 민팅 전에 모든 IPFS CID를 검증하세요
NFT 저장소 모니터링 및 유지 관리
컬렉션을 배포한 후:
- 정기 상태 검사: 메타데이터와 이미지가 액세스 가능한 상태로 유지되는지 확인
- 중요한 CID 백업: 업로드된 모든 콘텐츠 식별자의 기록을 보관
- 분석 모니터링: 액세스 패턴과 저장소 사용량 추적
- 확장 계획: 컬렉션이 커지면 피닝 서비스 업그레이드 고려
업로드를 프로그래밍 방식으로 관리하는 자세한 내용은 IPFS 업로드 API 튜토리얼을 참조하세요.
결론
NFT 메타데이터를 IPFS에 저장하면 디지털 자산이 장기적으로 액세스 가능하고 가치 있는 상태로 유지됩니다. 이 가이드를 따라 다음을 배웠습니다:
- ERC-721 준수 메타데이터 구조화
- Python과 JavaScript를 사용하여 자산과 메타데이터 업로드
- 적절한
tokenURI함수 구현 - 대규모 컬렉션을 위한 배치 작업 처리
- 프로덕션 배포를 위한 모범 사례 적용
IPFS의 분산 아키텍처와 신뢰할 수 있는 피닝 서비스의 조합은 시간의 시험을 견디는 성공적인 NFT 프로젝트의 기반을 만듭니다.
피닝을 시작할 준비가 되셨나요? 무료 계정 만들기 — 50개 파일, 1 GB 저장소, 월 2 GB 대역폭. 신용카드가 필요하지 않습니다.