’’’ [ 1단계: 메타데이터 생성 (클라이언트) ]
클라이언트(스프링 서버)가 POST /metadata를 호출합니다.
이때 contractAddress와 tokenId는 비워두고, imageUrl, utility 등 NFT의 내용물만 채워서 전송합니다. utility.type에 따라 JSON Schema 검증이 수행되며, 검증 실패 시 상세 에러가 반환됩니다. (utility.type 미지정/허용 타입 외는 오류)
DApp 서버는 Metadata 문서를 생성하고, 그 _id 값 (이하 metadataId)을 클라이언트에게 반환합니다. (예: “6750121f7b3d9f1a6c4c4051”). 인증서 타입(Type 4, Certificate)의 경우 서버가 canonical JSON으로 SHA-256 해시를 계산해 metadataHash를 제공합니다.
[ 2단계: 민팅 주문 (클라이언트) ]
클라이언트가 POST /minting/order를 호출합니다.
Request Body에 1단계에서 받은 metadataId: “6750121f7b3d9f1a6c4c4051”와 receiverAddress 등을 포함하여 전송합니다.
DApp 서버는 MintingOrder 문서를 PENDING 상태로 생성하고, 이 주문을 Bull 큐에 넣은 뒤, orderId를 즉시 반환합니다.
[ 3단계: “Link” 작업 (DApp 서버 내부) ] (★핵심★)
NestJS의 MintingProcessor(T-110)가 큐에서 작업을 꺼내 민팅을 실행합니다.
민팅이 성공하고(T-120), 프로세서는 txHash, 그리고 트랜잭션 로그에서 contractAddress와 실제 tokenId를 확보합니다.
프로세서는 MintingOrder의 상태를 CONFIRMED로 업데이트함과 동시에,
metadataId (“675…“)를 이용해 Metadata 컬렉션을 찾아, 비어있던 contractAddress와 tokenId 필드를 이 값들로 채워줍니다. (데이터 “Link” 완료) ‘’’
metadataBaseUri를 명시하거나 .env의 METADATA_BASE_URI를 통해 기본 값을 지정합니다.<base>/<contractAddress>/<base>/<contractAddress>/{id}
로 컨트랙트에 URI를 설정합니다.tokenURI를 통하면 자동으로 GET /metadata/:contractAddress/:tokenId 엔드포인트로 연결됩니다.로직 흐름도 요청 수신: metadataId, contractAddress가 포함된 주문 요청.
표준 확인: DeployedContract DB에서 해당 contractAddress의 erc_standard (“721” or “1155”) 조회.
점유 체크 (Validation):
ERC-721: “이 메타데이터 ID를 사용하는 진행 중(PENDING/PROCESSING)이거나 완료(CONFIRMED)된 주문이 하나라도 있는가?” → 있으면 Reject (409 Conflict).
ERC-1155: “이 메타데이터가 이미 다른 컨트랙트나, 다른 TokenID에 매핑(Link)되어 있는가?” → (이미 매핑된 경우) 요청 정보와 다르면 Reject. 아니면 Pass.
strict, allErrors: true, removeAdditional: false.GET /metadata/:contractAddress/:tokenId Redis 캐싱(민팅 링크 완료 시 캐시; 가변 필드 변경 시 무효화).POST /metadata
{
"chainId": "43114",
"name": "Flexible NFT",
"description": "Free-form metadata",
"imageUrl": "https://cdn.example.com/img/flex.png",
"attributes": [
{ "traitType": "Category", "value": "Misc" }
],
"utility": {
"type": "Flexible",
"data": { "note": "any structure allowed" }
},
"schemaVersion": "opensea_flexible@1.0.0"
}
POST /metadata
{
"chainId": "43114",
"name": "Concert Ticket",
"imageUrl": "https://cdn.example.com/img/ticket.png",
"attributes": [
{ "traitType": "Event Name", "value": "MIJI LIVE" },
{ "traitType": "Event Date", "value": 1737072000, "displayType": "date" },
{ "traitType": "Venue", "value": "Hall A" },
{ "traitType": "Zone", "value": "R" },
{ "traitType": "Seat", "value": 12 },
{ "traitType": "Status", "value": "Active" },
{ "traitType": "Transferable", "value": "Yes" }
],
"utility": {
"type": "Ticket",
"data": {
"ticket_id": "T-1001",
"event": { "name": "MIJI LIVE", "date": 1737072000 },
"seat": { "zone": "R", "seat": "12" },
"status": "Active"
}
},
"schemaVersion": "ticket_schema@1.0.0"
}
POST /metadata
{
"chainId": "43114",
"name": "10% Off Coupon",
"imageUrl": "https://cdn.example.com/img/coupon.png",
"attributes": [
{ "traitType": "Brand", "value": "MIJI" },
{ "traitType": "Product", "value": "T-Shirt" },
{ "traitType": "Price", "value": 10 },
{ "traitType": "Expires At", "value": 1739664000, "displayType": "date" },
{ "traitType": "Status", "value": "Active" }
],
"utility": {
"type": "Coupon",
"data": {
"token_id": "CP-001",
"brand": "MIJI",
"expired_at": 1739664000,
"status": "Active"
}
},
"schemaVersion": "coupon_schema@1.0.0"
}
POST /metadata
{
"chainId": "43114",
"name": "Completion Certificate",
"imageUrl": "https://cdn.example.com/img/cert.png",
"attributes": [
{ "traitType": "Type", "value": "Certificate" },
{ "traitType": "Serial Number", "value": "CERT-2025-0001" },
{ "traitType": "Style Name", "value": "Gold" },
{ "traitType": "Issuer (Mint by)", "value": "MIJI" },
{ "traitType": "Creator", "value": "MIJI EDU" },
{ "traitType": "Issue Date", "value": 1737072000, "displayType": "date" },
{ "traitType": "Transferability", "value": "Non-Transferable (SBT)" }
],
"utility": {
"type": "Certificate",
"data": {
"valid_until": 1768608000
}
},
"schemaVersion": "cert_schema@1.0.0"
}
Certificate는 서버가 canonical JSON으로 SHA-256 해시를 계산해 metadataHash를 생성합니다(민팅 시 certificate 컨트랙트에 전달). 요청에 metadataHash를 넣을 필요는 없습니다.