> 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/vi/phat-trien/nang-cao/lam-viec-voi-nft/duc-voi-thirdweb.md).

# Đúc NFT với thirdweb

[thirdweb](https://thirdweb.com/) là một nền tảng cung cấp công cụ và cơ sở hạ tầng để xây dựng các ứng dụng phi tập trung trên nhiều mạng blockchain. Nó đơn giản hóa việc tích hợp blockchain với các SDK, API và các thành phần dựng sẵn.

Trong trường hợp đúc NFT, thirdweb cung cấp một dashboard trực quan cùng tính năng tải lên IPFS, các hợp đồng đã được kiểm toán và bộ SDK phong phú.

## Đúc một NFT đơn lẻ thủ công

Nếu bạn chỉ muốn tải lên một hình ảnh và đúc nó, thirdweb cung cấp giao diện triển khai chỉ bằng vài cú nhấp chuột.

Đầu tiên, hãy tạo một tài khoản thirdweb bằng cách dùng ví Web3 của bạn làm chủ sở hữu. Điều này cho phép bạn truy cập vào dashboard thirdweb của mình.

Từ đó, bạn có thể chọn giữa hai hướng:

* Sử dụng trang [TokenERC721](https://thirdweb.com/thirdweb.eth/TokenERC721) và nút "Deploy Now" của họ.
* Hoặc sử dụng giao diện "Create Token" trong dashboard của họ.

Hãy cùng khám phá nhanh từng cách trên trang này.\
[Cổng thông tin dành cho nhà phát triển thirdweb](https://portal.thirdweb.com/) sẽ cung cấp thêm thông tin khi bạn cần.

### Sử dụng trang TokenERC721

1. Truy cập trang [TokenERC721](https://thirdweb.com/thirdweb.eth/TokenERC721). Trang này dựa trên hợp đồng của họ để tạo một Bộ sưu tập NFT.
2. Nhấp vào "Deploy Now" để truy cập trang triển khai hợp đồng.

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

3. Điền vào phần "Contract metadata":
   1. Tải lên hình ảnh mà bạn muốn đại diện cho bộ sưu tập NFT của bạn (hoặc chỉ hình ảnh bạn muốn đúc).
   2. Đặt một tên duy nhất và một ký hiệu/ticker cho bộ sưu tập của bạn.
   3. Viết mô tả cho bộ sưu tập.
4. thirdweb tự động điền các địa chỉ nhận cho "Primary Sales" và "Royalties", hãy đảm bảo rằng các địa chỉ Web3 đó đúng là địa chỉ ví của bạn.
5. Trong "Deploy Options", chọn Chiliz Chain (hoặc Chiliz Spicy Testnet nếu đây chỉ là thử nghiệm) và nhấp "Deploy Now":

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

6. thirdweb sẽ bắt đầu triển khai hợp đồng, kích hoạt một modal xác nhận từ ví Web3 của bạn. Xác nhận để hoàn tất việc triển khai.\
   ![](/files/GQCmJ9iRO1OxAdvrYB02)
7. Sau khi hoàn tất, nhấp vào nút "View Contract". Bạn sẽ được đưa đến trang hợp đồng trên dashboard thirdweb của mình, với một danh sách kiểm tra/thanh tiến trình:\
   ![](/files/3i9B7Dn9n0VDazwsNlTe)
8. Như bạn có thể thấy từ danh sách kiểm tra, cho đến lúc này bạn mới chỉ triển khai một hợp đồng, bạn vẫn cần thực sự đúc NFT. Ở cột bên trái, nhấp vào mục menu "NFTs" để mở một trang mới, hiện đang trống:\
   ![](/files/uUmmkJl7l2vkHznorusJ)
9. Nhấp vào nút "Mint" và một panel bên sẽ mở ra với các trường cho từng thuộc tính của NFT. Đây là nơi bạn tạo metadata cho NFT của mình:\
   ![](/files/wx3v7HneTVLbHYTXYeDA)
10. Điền vào các trường khác nhau. Bạn có thể tạo các trường dành riêng cho bộ sưu tập NFT của mình, chẳng hạn như "Artist\_name" hoặc "Type\_of\_work", và khám phá các tùy chọn nâng cao, nhưng để thử nghiệm này bạn có thể giữ nó tối giản: tên, tệp phương tiện, mô tả.
11. Nhấp vào nút "Mint NFT" ở cuối panel bên và phê duyệt giao dịch từ ví của bạn.\
    ![](/files/s2ENDCj3t98sVWIReTZr)
12. thirdweb sau đó sẽ hiển thị trang "NFTs", với NFT của bạn hiện đã hiển thị, được gắn vào hợp đồng NFT của bạn.

XONG! Bạn đã đúc NFT của mình!

Để kiểm tra rằng nó đã thực sự trên Chiliz Chain, hãy mở [Chiliscan](https://www.chiliscan.com/) (hoặc [phiên bản Testnet của nó](https://testnet.chiliscan.com/) nếu bạn đang dùng Spicy Testnet) và sao chép-dán hash của NFT vào trường tìm kiếm. Nó sẽ ngay lập tức xác nhận rằng hợp đồng của bạn đã có trên chain, và tab "Inventory" của nó sẽ liệt kê NFT của bạn với bạn (hoặc ít nhất là ID ví của bạn) là chủ sở hữu.

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

Nếu bạn đúc NFT trên Chiliz Chain Mainnet, bạn sẽ thấy nó xuất hiện trong tài khoản của bạn trên các marketplace như [Rarible](https://rarible.com/) hoặc [OKX](https://www.okx.com/), và từ đó bạn có thể bắt đầu bán nó!

### Sử dụng nút "Create Token"

Đây thực ra là một cách trực tiếp hơn so với cách trên, và là một bổ sung gần đây vào thirdweb.

1. Từ bất kỳ thư mục dự án nào trong dashboard thirdweb của bạn, nhấp vào tùy chọn "Tokens" trong thanh bên trái:\
   ![](/files/BjhGOM1f5D8163rHuMVK)
2. Trong trang "Tokens" mở ra, nhấp vào nút "Create Token" ở phía bên phải. Một cửa sổ modal mở ra với 2 tùy chọn, chọn "Create NFT Collection".\
   ![](/files/rRdqiYrZDbLsBtqEbk3g)
3. Điều này sẽ đưa bạn đến phiên bản từng bước của trang triển khai [TokenERC721](https://thirdweb.com/thirdweb.eth/TokenERC721) (thực tế, nó sẽ triển khai một hợp đồng ERC-721 Drop). Điền vào tất cả các trường cần thiết cho hợp đồng này, rồi nhấn nút "Next".
4. Màn hình tiếp theo là về NFT: tải lên hình ảnh và điền vào các trường mô tả hình ảnh của bạn, rồi nhấn nút "Next".
5. thirdweb hiện hiển thị tóm tắt bộ sưu tập NFT mà bạn sắp ra mắt. Kiểm tra rằng mọi thứ đều chính xác rồi nhấp vào nút "Launch NFT Collection".
6. thirdweb đảm nhận việc triển khai hợp đồng, đúc NFT và thiết lập các điều kiện, tất cả trong một lần. Bạn sẽ cần xác nhận ba giao dịch qua ví Web3 của mình.\
   ![](/files/PEylG1qX2L4CZ5iEyKDx)
7. Sau khi hoàn tất, nhấp vào nút "View NFT", và bạn sẽ được đưa trở lại dashboard thirdweb của mình, hiển thị trang dành riêng cho hợp đồng này — và các NFT được gắn kèm.

Từ đó, bạn có thể kiểm tra rằng nó đã thực sự trên chain thông qua block explorer, hoặc bắt đầu bán NFT của bạn qua một marketplace.

## Đúc một NFT đơn lẻ bằng lập trình

Đây là một đoạn mã mẫu sử dụng [thirdweb v5 SDK](https://portal.thirdweb.com/references/typescript/v5), tải tệp phương tiện lên IPFS, tạo tệp `metadata.json`, sau đó cũng tải nó lên IPFS.

Bạn sẽ cần có:

* Một hợp đồng ERC-721 đã được triển khai trên Chiliz Chain. Trong trường hợp này, bạn nên triển khai bằng thirdweb thay vì công cụ khác.
* Một API key của thirdweb, được liên kết với hợp đồng ERC-721 mà bạn đã triển khai qua thirdweb. Bạn có thể tìm thấy nó trong trang dự án của smart contract, với tên "Client ID".
* Cài đặt thirdweb SDK trên máy local. Bạn có thể thực hiện bằng `npm`:

```bash
npm install thirdweb dotenv
```

Tham số `dotenv` tạo ra một tệp `.env`, cần thiết để lưu trữ các giá trị riêng tư:

```
THIRDWEB_SECRET_KEY=YOUR_TW_SECRET_KEY # Từ trang dự án thirdweb của bạn.
PRIVATE_KEY=0xabc...                   # Ví Web3 sẽ gửi giao dịch.
CONTRACT_ADDRESS=0xYour721Address      # Hợp đồng TokenERC721 đã triển khai của bạn.
CHAIN_ID=88882                         # 88888=Mainnet, 88882=Spicy
IMAGE_PATH=./art/image.png             # Đường dẫn local đến tệp phương tiện của bạn.
NAME=My Chiliz NFT
DESCRIPTION=Minted on Chiliz Chain
```

{% hint style="danger" %}
Đừng bao giờ để lộ `PRIVATE_KEY` hay `THIRDWEB_SECRET_KEY` trong code phía client!\
Hãy giữ tệp này trên máy chủ an toàn/CI, và đừng tải nó lên kho lưu trữ công khai.
{% endhint %}

Bây giờ bạn đã sẵn sàng, bạn có thể lấy cảm hứng từ script này, sẽ:

1. Tải tệp phương tiện lên IPFS
2. Xây dựng và tải tệp `metadata.json` lên
3. Đúc NFT bằng phương thức mintTo của hợp đồng.
4. Hiển thị `tokenId` và `tokenUrl` kết quả cho bạn.

{% hint style="success" %}
Đây chỉ là mã mẫu, bạn sẽ cần điều chỉnh nó cho phù hợp với code dự án của riêng bạn!
{% 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 %}

Các phần quan trọng để tham khảo là:

* `upload` lưu trữ các tệp (và JSON) lên IPFS và trả về các URI `ipfs://…`.
* `mintTo` chấp nhận một chuỗi để sử dụng trực tiếp làm `tokenURI`.
* Chúng ta phân tích sự kiện `Transfer` ERC-721 tiêu chuẩn để đọc `tokenId` đã được đúc.

## Đúc một bộ sưu tập NFT bằng lập trình

Đúc một bộ sưu tập NFT không khác nhiều so với đúc một NFT đơn lẻ. Sự khác biệt đáng chú ý nhất là tệp `.env` của bạn trỏ đến thư mục chứa tất cả các hình ảnh bằng `IMAGE_DIR`, thay vì một hình ảnh đơn lẻ với `IMAGE_PATH`.

```dotenv
THIRDWEB_SECRET_KEY=YOUR_TW_SECRET_KEY # Từ trang dự án thirdweb của bạn.
PRIVATE_KEY=0xabc...                   # Ví Web3 sẽ gửi giao dịch.
CONTRACT_ADDRESS=0xYour721Address      # Hợp đồng TokenERC721 đã triển khai của bạn.
CHAIN_ID=88882                         # 88888=Mainnet, 88882=Spicy
IMAGE_PATH=./art                       # Đường dẫn local đến các tệp phương tiện của bạn.
NAME_PREFIX=My Chiliz NFT              # Sẽ trở thành: "My Chiliz NFT <n>"
DESCRIPTION=Minted on Chiliz Chain
```

Mã mẫu mà chúng tôi trình bày ở đây do đó có tính đến điều này:

{% 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 %}

Các thay đổi chi tiết là:

* `IMAGES_DIR` thay thế `IMAGE_PATH`.
* Thêm `listMediaFiles` để thu thập nhiều đầu vào.
* Tải lên theo lô (`upload`) cho hình ảnh và metadata (giữ nguyên thứ tự).
* Vòng lặp để `mintTo` từng `metadataUri`, phân tích từng sự kiện `Transfer` để lấy `tokenId` của nó.
* Tên được đánh số tự động: `NAME #1`, `NAME #2`, … sử dụng `NAME`/`DESCRIPTION` hiện có của bạn.

Một lần nữa, hãy sử dụng đây làm nguồn cảm hứng cho code của riêng bạn, đừng dùng nguyên xi!

Lưu ý: Nếu bạn muốn thực hiện lazy minting, hợp đồng ERC-721 của thirdweb có [phương thức `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/vi/phat-trien/nang-cao/lam-viec-voi-nft/duc-voi-thirdweb.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.
