> For the complete documentation index, see [llms.txt](https://docs.chiliz.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.chiliz.com/ko/gaebal/goseup/nft-jageob/thirdweb-mintting.md).

# thirdweb으로 민팅

[thirdweb](https://thirdweb.com/)은 여러 블록체인 네트워크에서 분산형 애플리케이션을 구축하기 위한 도구와 인프라를 제공하는 플랫폼입니다. SDK, API, 사전 제작된 컴포넌트로 블록체인 통합을 간소화합니다.

NFT 민팅의 경우, thirdweb은 감사된 컨트랙트와 광범위한 SDK와 함께 세련된 대시보드 및 IPFS 업로드 기능을 제공합니다.

## 단일 NFT 수동 민팅하기

단일 이미지를 업로드하고 민팅하려는 경우, thirdweb은 클릭으로 배포할 수 있는 인터페이스를 제공합니다.

먼저, Web3 지갑을 소유자로 하여 thirdweb 계정을 생성하세요. 그러면 thirdweb 대시보드에 접근할 수 있습니다.

여기에서 두 가지 경로 중 하나를 선택할 수 있습니다:

* [TokenERC721](https://thirdweb.com/thirdweb.eth/TokenERC721) 페이지와 "Deploy Now" 버튼 사용.
* 또는 대시보드의 "Create Token" 인터페이스 사용.

이 페이지에서 각각을 간략히 살펴보겠습니다.\
더 자세한 정보는 [thirdweb Developer Portal](https://portal.thirdweb.com/)을 참고하세요.

### TokenERC721 페이지 사용하기

1. [TokenERC721](https://thirdweb.com/thirdweb.eth/TokenERC721) 페이지로 이동합니다. 이것은 NFT 컬렉션 생성을 위한 컨트랙트를 사용합니다.
2. "Deploy Now"를 클릭하여 컨트랙트 배포 페이지에 접근합니다.

<figure><img src="/files/odFqFoEGIqVrjBviPTLk" alt=""><figcaption></figcaption></figure>

3. "Contract metadata" 섹션을 입력합니다:
   1. NFT 컬렉션(또는 민팅할 이미지)을 나타낼 이미지를 업로드합니다.
   2. 컬렉션에 고유한 이름과 심볼/티커를 지정합니다.
   3. 설명을 입력합니다.
4. thirdweb은 "Primary Sales"와 "Royalties"의 수신자 주소를 자동으로 채웁니다. Web3 주소가 실제 지갑 주소인지 확인하세요.
5. "Deploy Options"에서 Chiliz Chain(테스트 목적이라면 Chiliz Spicy Testnet)을 선택하고 "Deploy Now"를 클릭합니다:

<figure><img src="/files/gcAzmPDgBgWCLhrtUlL2" alt=""><figcaption></figcaption></figure>

6. thirdweb이 컨트랙트 배포를 시작하면, Web3 지갑에서 확인 모달이 트리거됩니다. 배포를 완료하려면 확인하세요.\
   ![](/files/52ry61mfcMWwIy2Ijt1p)
7. 완료되면 "View Contract" 버튼을 클릭합니다. 체크리스트/진행 표시줄이 있는 thirdweb 대시보드의 컨트랙트 페이지로 이동합니다:\
   ![](/files/oyj9tKB0dT7B49iXDZBE)
8. 체크리스트에서 볼 수 있듯이, 지금까지는 컨트랙트를 배포하기만 한 것입니다. 실제로 NFT를 민팅해야 합니다. 왼쪽 열에서 "NFTs" 메뉴 항목을 클릭하여 현재 비어 있는 새 페이지를 엽니다:\
   ![](/files/2pBXxgYHfaYIUia7zmuK)
9. "Mint" 버튼을 클릭하면 NFT의 각 속성에 대한 필드가 있는 사이드 패널이 열립니다. 여기서 NFT의 메타데이터를 생성합니다:\
   ![](/files/7W2CzMFJY81S7ru1ZyYV)
10. 다양한 필드를 입력합니다. NFT 컬렉션에 특화된 필드(예: "Artist\_name" 또는 "Type\_of\_work")를 생성하고 고급 옵션을 탐색할 수 있지만, 테스트를 위해서는 최소한으로 유지할 수 있습니다: 이름, 미디어 파일, 설명.
11. 사이드 패널 하단의 "Mint NFT" 버튼을 클릭하고 지갑에서 트랜잭션을 승인합니다.\
    ![](/files/k7jl5tI5Xfuva1ssmAPT)
12. 그러면 thirdweb이 "NFTs" 페이지를 표시하며, NFT가 NFT 컨트랙트에 연결되어 표시됩니다.

완료! NFT를 민팅했습니다!

실제로 Chiliz Chain에 있는지 확인하려면 [Chiliscan](https://www.chiliscan.com/)(Spicy Testnet을 사용하는 경우 [테스트넷 버전](https://testnet.chiliscan.com/))을 열고 NFT의 해시를 검색창에 붙여넣으세요. 즉시 컨트랙트가 체인에 있음을 확인해 주며, "Inventory" 탭에 귀하(또는 적어도 귀하의 지갑 ID)가 소유자로 NFT가 나열됩니다.

<figure><img src="/files/rAA22xu03rXVxSK2oN7d" alt=""><figcaption></figcaption></figure>

Chiliz Chain Mainnet에서 NFT를 민팅한 경우, [Rarible](https://rarible.com/) 또는 [OKX](https://www.okx.com/)와 같은 마켓플레이스의 계정에서 NFT가 표시되며, 거기서 판매를 시작할 수 있습니다!

### "Create Token" 버튼 사용하기

이것은 위의 방법보다 더 간단한 방법으로, thirdweb에 최근 추가된 기능입니다.

1. thirdweb 대시보드의 프로젝트 폴더에서 왼쪽 사이드바의 "Tokens" 옵션을 클릭합니다:\
   ![](/files/tmc5cekubOXgdqp0PN46)
2. 열린 "Tokens" 페이지에서 오른쪽의 "Create Token" 버튼을 클릭합니다. 2가지 옵션이 있는 모달 창이 열리면 "Create NFT Collection"을 선택합니다.\
   ![](/files/GV9C5j6uV6URcknG8zSz)
3. [TokenERC721](https://thirdweb.com/thirdweb.eth/TokenERC721) 배포 페이지의 단계별 버전으로 이동합니다(실제로 ERC-721 Drop 컨트랙트를 배포합니다). 이 컨트랙트에 필요한 모든 필드를 입력한 후 "Next" 버튼을 누릅니다.
4. 다음 화면은 NFT 자체에 관한 것입니다: 이미지를 업로드하고 이미지를 설명하는 필드를 입력한 후 "Next" 버튼을 누릅니다.
5. thirdweb이 출시하려는 NFT 컬렉션의 요약을 표시합니다. 모든 것이 올바른지 확인한 후 "Launch NFT Collection" 버튼을 클릭합니다.
6. thirdweb이 컨트랙트 배포, NFT 민팅, 조건 설정을 한 번에 처리합니다. Web3 지갑을 통해 세 가지 트랜잭션을 확인해야 합니다.\
   ![](/files/HpLcVP5hNaueXcm7cDbR)
7. 완료되면 "View NFT" 버튼을 클릭하면 이 컨트랙트와 연결된 NFT를 표시하는 특정 컨트랙트 페이지로 thirdweb 대시보드에 돌아갑니다.

여기서 블록 탐색기를 통해 실제로 체인에 있는지 확인하거나 마켓플레이스를 통해 NFT 판매를 시작할 수 있습니다.

## 단일 NFT 프로그래밍 방식으로 민팅하기

다음은 미디어 파일을 IPFS에 업로드하고, `metadata.json` 파일을 생성한 후 IPFS에 업로드하는 [thirdweb v5 SDK](https://portal.thirdweb.com/references/typescript/v5)를 사용한 코드 샘플입니다.

다음이 필요합니다:

* Chiliz Chain에 이미 배포된 ERC-721 컨트랙트. 이 경우 다른 도구가 아닌 thirdweb을 사용하여 배포해야 합니다.
* thirdweb API 키(thirdweb을 통해 배포한 ERC-721 컨트랙트에 연결). 스마트 컨트랙트 프로젝트 페이지에서 "Client ID"라는 이름으로 찾을 수 있습니다.
* thirdweb SDK 로컬 설치. `npm`을 사용하여 설치할 수 있습니다:

```bash
npm install thirdweb dotenv
```

`dotenv` 파라미터는 개인 값을 저장하는 데 필요한 `.env` 파일을 생성합니다:

```
THIRDWEB_SECRET_KEY=YOUR_TW_SECRET_KEY # From your thirdweb project's page.
PRIVATE_KEY=0xabc...                   # The Web3 wallet that will send the transaction.
CONTRACT_ADDRESS=0xYour721Address      # Your deployed TokenERC721 contract.
CHAIN_ID=88882                         # 88888=Mainnet, 88882=Spicy
IMAGE_PATH=./art/image.png             # The local path to your media file.
NAME=My Chiliz NFT
DESCRIPTION=Minted on Chiliz Chain
```

{% hint style="danger" %}
`PRIVATE_KEY`와 `THIRDWEB_SECRET_KEY`를 클라이언트 측 코드에 절대 노출하지 마세요!\
이 파일은 보안 서버/CI에 보관하고, 공개 저장소에 업로드하지 마세요.
{% endhint %}

준비가 완료되었다면, 다음 스크립트를 참고하여 아래 작업을 수행할 수 있습니다:

1. 미디어 파일을 IPFS에 업로드합니다.
2. `metadata.json` 파일을 빌드하고 업로드합니다.
3. 컨트랙트의 mintTo 메서드를 사용하여 NFT를 민팅합니다.
4. 결과 `tokenId`와 `tokenUrl`을 표시합니다.

{% hint style="success" %}
이것은 샘플 코드일 뿐이므로, 자신의 프로젝트 코드에 맞게 조정해야 합니다!
{% endhint %}

{% code overflow="wrap" lineNumbers="true" fullWidth="true" %}

```typescript
import 'dotenv/config';
import fs from 'fs';
import path from 'path';

import { createThirdwebClient, getContract, sendTransaction, waitForReceipt, parseEventLogs } from "thirdweb";
import { defineChain } from "thirdweb/chains";
import { privateKeyToAccount } from "thirdweb/wallets";
import { mintTo, transferEvent, getNFT } from "thirdweb/extensions/erc721";
import { upload } from "thirdweb/storage";

function guessMime(p: string) {
  const ext = path.extname(p).toLowerCase();
  if (ext === ".png") return "image/png";
  if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
  if (ext === ".webp") return "image/webp";
  if (ext === ".gif") return "image/gif";
  if (ext === ".mp4") return "video/mp4";
  if (ext === ".webm") return "video/webm";
  return "application/octet-stream";
}

async function main() {
  const client = createThirdwebClient({ secretKey: process.env.THIRDWEB_SECRET_KEY! });
  const chain = defineChain(Number(process.env.CHAIN_ID)); // 88888 or 88882
  const account = privateKeyToAccount({ client, privateKey: process.env.PRIVATE_KEY! });

  const contract = getContract({
    client,
    chain,
    address: process.env.CONTRACT_ADDRESS as `0x${string}`,
  });

  // Upload media file to IPFS
  const filePath = process.env.IMAGE_PATH!;
  const buffer = fs.readFileSync(filePath);
  const file = new File([buffer], path.basename(filePath), { type: guessMime(filePath) });

  const [imageUri] = await upload({ client, files: [file] }); // returns ipfs://... 
  // console.log("imageUri:", imageUri);

  // Build & upload metadata.json referencing IPFS imageUri
  const metadata = {
    name: process.env.NAME!,
    description: process.env.DESCRIPTION!,
    image: imageUri
  };
  const metaBlob = new Blob([JSON.stringify(metadata, null, 2)], { type: "application/json" });
  const metaFile = new File([metaBlob], "metadata.json", { type: "application/json" });

  const [metadataUri] = await upload({ client, files: [metaFile] });
  // console.log("metadataUri:", metadataUri);

  // Prepare & send mintTo transaction using the metadata URI
  // mintTo accepts a string to use directly as tokenURI.
  const tx = mintTo({
    contract,
    to: account.address,
    nft: metadataUri, // The tokenURI
  });

  const { transactionHash } = await sendTransaction({ transaction: tx, account });
  //console.log("tx:", transactionHash);

  const receipt = await waitForReceipt({ client, chain, transactionHash });

  // Parse Transfer event to display tokenId
  const events = parseEventLogs({ logs: receipt.logs, events: [transferEvent()] });
  const minted = events.find(e => e.eventName === "Transfer" && e.args.from === "0x0000000000000000000000000000000000000000");
  const tokenId = minted ? (minted.args.tokenId as bigint) : undefined;
  //console.log("tokenId:", tokenId?.toString() ?? "(not found)");

  if (tokenId !== undefined) {
    const nft = await getNFT({ contract, tokenId });
    //console.log("tokenURI (read back):", nft.tokenURI);
  }
}

main().catch((err) => (console.error(err), process.exit(1)));
```

{% endcode %}

주요 참고 사항:

* `upload`는 파일(및 JSON)을 IPFS에 저장하고 `ipfs://…` URI를 반환합니다.
* `mintTo`는 문자열을 직접 `tokenURI`로 사용합니다.
* 표준 ERC-721 `Transfer` 이벤트를 파싱하여 민팅된 `tokenId`를 읽습니다.

## NFT 컬렉션 프로그래밍 방식으로 민팅하기

NFT 컬렉션 민팅은 단일 NFT 민팅과 크게 다르지 않습니다. 가장 주목할 만한 차이점은 `.env` 파일이 `IMAGE_PATH`로 단일 이미지를 지정하는 대신 `IMAGE_DIR`을 사용하여 모든 이미지가 있는 폴더를 가리킨다는 것입니다.

```dotenv
THIRDWEB_SECRET_KEY=YOUR_TW_SECRET_KEY # From your thirdweb project's page.
PRIVATE_KEY=0xabc...                   # The Web3 wallet that will send the transaction.
CONTRACT_ADDRESS=0xYour721Address      # Your deployed TokenERC721 contract.
CHAIN_ID=88882                         # 88888=Mainnet, 88882=Spicy
IMAGE_PATH=./art                       # The local path to your media files.
NAME_PREFIX=My Chiliz NFT              # This will become: "My Chiliz NFT <n>"
DESCRIPTION=Minted on Chiliz Chain
```

따라서 여기서 제시하는 샘플 코드는 이를 고려합니다:

{% code overflow="wrap" lineNumbers="true" fullWidth="true" %}

```typescript
import 'dotenv/config';
import fs from 'fs';
import path from 'path';

import { createThirdwebClient, getContract, sendTransaction, waitForReceipt, parseEventLogs } from "thirdweb";
import { defineChain } from "thirdweb/chains";
import { privateKeyToAccount } from "thirdweb/wallets";
import { mintTo, transferEvent, getNFT } from "thirdweb/extensions/erc721";
import { upload } from "thirdweb/storage";

function guessMime(p: string) {
  const ext = path.extname(p).toLowerCase();
  if (ext === ".png") return "image/png";
  if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
  if (ext === ".webp") return "image/webp";
  if (ext === ".gif") return "image/gif";
  if (ext === ".mp4") return "video/mp4";
  if (ext === ".webm") return "video/webm";
  return "application/octet-stream";
}

// One way to gather all files
function listMediaFiles(dir: string) {
  const allow = new Set([".png", ".jpg", ".jpeg", ".webp", ".gif", ".mp4", ".webm", ".svg"]);
  return fs
    .readdirSync(dir)
    .filter((f) => allow.has(path.extname(f).toLowerCase()))
    .map((f) => path.join(dir, f));
}

async function main() {
  const client = createThirdwebClient({ secretKey: process.env.THIRDWEB_SECRET_KEY! });
  const chain = defineChain(Number(process.env.CHAIN_ID)); // 88888 or 88882
  const account = privateKeyToAccount({ client, privateKey: process.env.PRIVATE_KEY! }); 

  const contract = getContract({
    client,
    chain,
    address: process.env.CONTRACT_ADDRESS as `0x${string}`,
  });

  // Upload media files in a folder to IPFS
  const dirPath = process.env.IMAGES_DIR!;
  const filePaths = listMediaFiles(dirPath);
  if (!filePaths.length) {
    throw new Error(`No media files found in ${dirPath}`);
  }

  const fileObjs = filePaths.map((p) => {
    const buffer = fs.readFileSync(p);
    return new File([buffer], path.basename(p), { type: guessMime(p) });
  });

  const imageUris = await upload({ client, files: fileObjs }); // returns ["ipfs://...", ...]
  //console.log("Uploaded images to IPFS:", imageUris.length, "files");

  // Build & upload each metadata.json referencing its imageUri
  // Uses NAME and DESCRIPTION envs; names become "<NAME> #<index>"
  const namePrefix = process.env.NAME!;
  const description = process.env.DESCRIPTION!;

  // Create an array of File objects for metadata JSONs
  const metaFiles = imageUris.map((imageUri, i) => {
    const metadata = {
      name: `${namePrefix} #${i + 1}`,
      description,
      image: imageUri
    };
    const metaBlob = new Blob([JSON.stringify(metadata, null, 2)], { type: "application/json" });
    return new File([metaBlob], `metadata-${i + 1}.json`, { type: "application/json" });
  });

  const metadataUris = await upload({ client, files: metaFiles }); // ["ipfs://.../metadata-1.json", ...]
  //console.log("Uploaded metadata files to IPFS:", metadataUris.length);

  // Prepare & send mintTo transactions using the metadata URIs (sequential)
  const ZERO = "0x0000000000000000000000000000000000000000";

  for (let i = 0; i < metadataUris.length; i++) {
    const metadataUri = metadataUris[i];

    const tx = mintTo({
      contract,
      to: account.address,
      nft: metadataUri, // The tokenURI
    });

    const { transactionHash } = await sendTransaction({ transaction: tx, account });
    console.log(`[${i + 1}/${metadataUris.length}] tx:`, transactionHash);

    const receipt = await waitForReceipt({ client, chain, transactionHash });

    // Parse Transfer event to get tokenId for this mint
    const events = parseEventLogs({ logs: receipt.logs, events: [transferEvent()] });
    const minted = events.find(
      (e) => e.eventName === "Transfer" && typeof e.args.from === "string" && e.args.from.toLowerCase() === ZERO
    );
    const tokenId = minted ? (minted.args.tokenId as bigint) : undefined;

    // console.log(`[${i + 1}/${metadataUris.length}] tokenId:`, tokenId?.toString() ?? "(not found)");
    if (tokenId !== undefined) {
      const nft = await getNFT({ contract, tokenId });
      // console.log(`[${i + 1}/${metadataUris.length}] tokenURI (read back):`, nft.tokenURI);
    }
  }
}

main().catch((err) => (console.error(err), process.exit(1)));

```

{% endcode %}

주요 변경 사항은 다음과 같습니다:

* `IMAGES_DIR`이 `IMAGE_PATH`를 대체합니다.
* 여러 입력을 수집하기 위한 `listMediaFiles`가 추가되었습니다.
* 이미지와 메타데이터를 일괄 `upload`합니다(순서 유지).
* 각 `metadataUri`를 `mintTo`하는 루프로, 각 `Transfer` 이벤트에서 `tokenId`를 파싱합니다.
* 이름은 기존 `NAME`/`DESCRIPTION`을 사용하여 자동 번호 매김됩니다: `NAME #1`, `NAME #2`, ….

다시 한번 강조드립니다. 이것은 자신의 코드에 영감을 얻기 위한 것이며, 그대로 사용하지 마세요!

참고: 레이지 민팅을 선호한다면, thirdweb의 ERC-721 컨트랙트에 [`lazyMint` 메서드](https://portal.thirdweb.com/references/typescript/v5/erc721/lazyMint)가 있습니다.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.chiliz.com/ko/gaebal/goseup/nft-jageob/thirdweb-mintting.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
