> 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/jp/kaihatsu/joukyu/nft/viem-mint.md).

# viem を使った mint

minting プロセスをきめ細かく制御したい開発者は、EVM ベースのプロジェクト向けの TypeScript インターフェースである [viem](https://viem.sh/) を使い、素の TypeScript で実装できます。[Ethers.js ライブラリ](https://ethers.org/) も堅牢なアプローチです。

*vanilla* で進める場合、thirdweb のようなフレームワークに頼るときよりも少し多くの作業が必要になります。たとえば、自前で IPFS node を運用しない限り、有料の NFT ストレージプラットフォームに頼る必要があります。

{% hint style="success" %}
この例では IPFS host として [Pinata](https://pinata.cloud/) を使用します。\
また、[OpenZeppelin ERC-721 contract](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol) を Chiliz Chain 上に自分で deploy する必要があります。[Remix IDE](https://remix.ethereum.org/) を使えば、ブラウザ内の IDE でこれを行えます。block explorer を使って contract を verify するのを忘れずに!
{% endhint %}

それでは、`viem`、`Pinata SDK`、そして `dotenv` モジュールをインストールしましょう:

```bash
npm i viem pinata dotenv
```

ニーズに合わせて `.env` ファイルを設定します:

```
# Wallet / contract
PRIVATE_KEY=0xabc...                      # server-side only
CONTRACT_ADDRESS=0xYourErc721Address
RECIPIENT=0xRecipientOrLeaveEmpty         # optional; defaults to minter

# Pinata
PINATA_JWT=eyJhbGciOi...                  # JWT from Pinata dashboard
PINATA_GATEWAY=your-subdomain.mypinata.cloud

# Single mint
IMAGE_PATH=./art/image.png
NAME=My Chiliz NFT
DESCRIPTION=Minted on Chiliz with viem

# Batch mint
IMAGES_DIR=./art
NAME_PREFIX=My Chiliz NFT
BATCH_DESCRIPTION=Batch minted on Chiliz with viem
```

それでは、コードを見ていきましょう。\
\&#xNAN;*そのまま使うのではなく、参考にしてください!*

## 単一の NFT を mint する

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

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

import { createWalletClient, createPublicClient, http, parseEventLogs } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import type { Address } from 'viem';
import { chiliz, spicy } from 'viem/chains';
import { PinataSDK } from 'pinata';

// Minimal ABI: safeMint(to, uri) + tokenURI + Transfer
const abi = [
  { type: 'function', name: 'safeMint', stateMutability: 'nonpayable',
    inputs: [{ name: 'to', type: 'address' }, { name: 'uri', type: 'string' }], outputs: [] },
  { type: 'function', name: 'tokenURI', stateMutability: 'view',
    inputs: [{ name: 'tokenId', type: 'uint256' }], outputs: [{ type: 'string' }] },
  { type: 'event', name: 'Transfer',
    inputs: [
      { name: 'from', type: 'address', indexed: true },
      { name: 'to', type: 'address', indexed: true },
      { name: 'tokenId', type: 'uint256', indexed: true },
    ]},
] as const;

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() {
  // Choose your network: use `spicy` for Spicy Testnet, switch to `chiliz` for Chiliz Chain Mainnet.
  const chain = spicy; // Change to `chiliz` for Mainnet

  const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
  const walletClient = createWalletClient({ account, chain, transport: http() });
  const publicClient = createPublicClient({ chain, transport: http() });

  const address = process.env.CONTRACT_ADDRESS as Address;
  const recipient = (process.env.RECIPIENT as Address) || account.address;

  // IPFS upload (Pinata)
  const pinata = new PinataSDK({
    pinataJwt: process.env.PINATA_JWT!,
    pinataGateway: process.env.PINATA_GATEWAY!,
  });

  const filePath = process.env.IMAGE_PATH!;
  const fileBlob = new Blob([fs.readFileSync(filePath)], { type: guessMime(filePath) });
  const fileObj = new File([fileBlob], path.basename(filePath), { type: guessMime(filePath) });

  const up = await pinata.upload.public.file(fileObj);
  const imageUri = `ipfs://${up.cid}`;

  const meta = await pinata.upload.public.json({
    name: process.env.NAME!,
    description: process.env.DESCRIPTION!,
    image: imageUri,
  });
  const metadataUri = `ipfs://${meta.cid}`;

  // Mint on-chain via viem
  const txHash = await walletClient.writeContract({
    address,
    abi,
    functionName: 'safeMint',
    args: [recipient, metadataUri],
  });

  const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });

  const ZERO = '0x0000000000000000000000000000000000000000';
  const logs = parseEventLogs({ abi, logs: receipt.logs, eventName: 'Transfer' });
  const mintLog = logs.find(l => (l.args as any).from?.toLowerCase?.() === ZERO);
  const tokenId = mintLog ? (mintLog.args as any).tokenId : undefined;

  console.log('tx:', txHash, '| tokenId:', tokenId ?? '(not parsed)', '| tokenURI:', metadataUri);
}

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

```

{% endcode %}

## NFT のコレクションを mint する

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

```typescript
import 'dotenv/config';
import fs from 'fs';
import path from 'path';
// import { Blob } from 'buffer'; // Node 18: uncomment the next line to get Blob.

import { createWalletClient, createPublicClient, http, parseEventLogs } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import type { Address } from 'viem';
import { chiliz, spicy } from 'viem/chains';
import { PinataSDK } from 'pinata';

// Minimal ABI: safeMint(to, uri) + tokenURI + Transfer
const abi = [
  { type: 'function', name: 'safeMint', stateMutability: 'nonpayable',
    inputs: [{ name: 'to', type: 'address' }, { name: 'uri', type: 'string' }], outputs: [] },
  { type: 'function', name: 'tokenURI', stateMutability: 'view',
    inputs: [{ name: 'tokenId', type: 'uint256' }], outputs: [{ type: 'string' }] },
  { type: 'event', name: 'Transfer',
    inputs: [
      { name: 'from', type: 'address', indexed: true },
      { name: 'to', type: 'address', indexed: true },
      { name: 'tokenId', type: 'uint256', indexed: true },
    ]},
] as const;

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() {
  // Choose your network: use `spicy` for Spicy Testnet, switch to `chiliz` for Mainnet.
  const chain = spicy; // Change to `chiliz` for Mainnet

  const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
  const walletClient = createWalletClient({ account, chain, transport: http() });
  const publicClient = createPublicClient({ chain, transport: http() });

  const address = process.env.CONTRACT_ADDRESS as Address;
  const recipient = (process.env.RECIPIENT as Address) || account.address;

  // IPFS upload (Pinata)
  const pinata = new PinataSDK({
    pinataJwt: process.env.PINATA_JWT!,
    pinataGateway: process.env.PINATA_GATEWAY!, // optional; not needed for ipfs:// URIs
  });

  const filePath = process.env.IMAGE_PATH!;
  const fileBlob = new Blob([fs.readFileSync(filePath)], { type: guessMime(filePath) });

  // Pass a Blob directly
  const up = await pinata.upload.public.file(fileBlob);
  const imageUri = `ipfs://${up.cid}`;

  const meta = await pinata.upload.public.json({
    name: process.env.NAME!,
    description: process.env.DESCRIPTION!,
    image: imageUri,
  });
  const metadataUri = `ipfs://${meta.cid}`;

  // Mint on-chain via viem
  const txHash = await walletClient.writeContract({
    address,
    abi,
    functionName: 'safeMint',
    args: [recipient, metadataUri],
  });

  const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });

  const ZERO = '0x0000000000000000000000000000000000000000';
  const logs = parseEventLogs({ abi, logs: receipt.logs, eventName: 'Transfer' });
  const mintLog = logs.find(l => (l.args as any).from?.toLowerCase?.() === ZERO);
  const tokenId = mintLog ? (mintLog.args as any).tokenId : undefined;

  console.log('tx:', txHash, '| tokenId:', tokenId ?? '(not parsed)', '| tokenURI:', metadataUri);
}

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

```

{% endcode %}


---

# 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:

```
GET https://docs.chiliz.com/jp/kaihatsu/joukyu/nft/viem-mint.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
