---
name: nft-drop
description: Use when the user wants to deploy an ERC-721A NFT collection, pin images + metadata to IPFS, batch-mint, or otherwise ship an NFT drop from this repo. Triggers on phrases like "deploy NFT", "mint NFTs", "launch a collection", "drop NFTs", "upload to IPFS", "NFT collection on Ethereum/Sepolia", or any request that involves the `nft-mint` CLI.
---

# nft-drop — shipping an ERC-721A collection

You are helping the user ship an NFT collection using this repo's `nft-mint` CLI (ERC-721A contract + Pinata IPFS + Foundry). The CLI is designed for one-command drops — lean into that instead of composing low-level primitives.

## The one command that does everything

```bash
node packages/cli/dist/index.js drop \
  --manifest <path-to-manifest.json> \
  --network sepolia \
  --mint \
  --no-prompt
```

`drop` runs upload → deploy → batch-mint in sequence. All the primitives (`upload`, `deploy`, `batch-mint`) also exist as standalone commands for advanced flows, but prefer `drop` unless the user explicitly needs to split the steps.

## When the user asks to deploy / mint an NFT collection

Follow this order — don't skip ahead:

### 1. Preflight (always do this first)

Run these checks in parallel via Bash:

- `test -f .env && echo ok` — user must have a `.env` file; if not, tell them to `cp .env.example .env` and fill it in.
- `test -f packages/cli/dist/index.js && echo ok` — CLI must be built; if missing, run `pnpm --filter @nft-minter/cli build`.
- `command -v forge >/dev/null && forge --version` — Foundry must be installed; if missing, point them to https://book.getfoundry.sh/getting-started/installation.

Check that `.env` has non-placeholder values for: `PRIVATE_KEY`, `RPC_URL_SEPOLIA` (or `_MAINNET`), `PINATA_JWT`, `ETHERSCAN_API_KEY`. Do **not** print their values — just confirm presence with something like `grep -cE "^PINATA_JWT=.{20,}$" .env`.

If any are missing, stop and tell the user exactly which key is missing and where to get it:

- `PINATA_JWT` — https://app.pinata.cloud → API Keys → New Key, V3 Resources → Files: Write
- `RPC_URL_SEPOLIA` — Alchemy / Infura free tier is fine
- `ETHERSCAN_API_KEY` — https://etherscan.io/myapikey
- `PRIVATE_KEY` — **burner wallet only for testnet**; never dev wallet on mainnet

### 2. Scaffold if they don't have a manifest yet

Ask these questions (batch them — don't ping-pong):

- Collection name?
- How many tokens?
- Target network (sepolia default, mainnet only if they're sure)?
- Mint price in ETH (0 default — owner-only mint is free anyway)?

Then:

```bash
node packages/cli/dist/index.js init <slug> --name "<name>" --count <n>
```

This creates `<slug>/manifest.json` + `<slug>/images/` with a README explaining what to drop in there.

### 3. Get the images in place

Tell the user to copy their images into `<slug>/images/` named `1.png`, `2.png`, … `<n>.png` (matching the `file` field in the manifest). Any image format works (png/jpg/gif/webp/svg).

If the user has oddly-named images or custom metadata (traits, descriptions per token), edit `<slug>/manifest.json` directly — the `file` field can point to any relative path. Keep `ManifestSchema` valid (see [packages/shared/src/metadata.ts](packages/shared/src/metadata.ts)).

Verify image count matches manifest:

```bash
jq '.tokens | length' <slug>/manifest.json
ls <slug>/images/ | grep -cEv 'README'
```

### 4. Ship it

Default one-shot:

```bash
node packages/cli/dist/index.js drop \
  --manifest <slug>/manifest.json \
  --network <network> \
  --price <eth> \
  --mint
```

Drop `--no-prompt` for CI or when the user says "don't ask me". Drop `--mint` if they only want to deploy the contract without minting tokens yet.

### 5. Report what happened

After `drop` succeeds, pull these out of its output and show the user:

- Contract address
- Etherscan link (auto-printed)
- NFT viewer link — OpenSea for mainnet, Etherscan's `/nft/` page for testnets (OpenSea discontinued testnet support in 2025)
- Images folder CID + Metadata folder CID

Suggest the next step based on their goal:

- "View the NFT" — on mainnet, OpenSea; on Sepolia / other testnets, the printed Etherscan NFT URL. Metadata indexing can lag a few minutes on either.
- "Mint more to another wallet" — `nft-mint mint --contract <addr> --to <addr> --quantity <n> --network <net>`
- "Set a mint price later" — call `setMintPrice(priceWei)` via `cast` or a follow-up script
- "Make it public mintable" — already is, `mint()` is payable and open to anyone; owner keeps `ownerMint` (free)

## Gotchas to watch for

- **Metadata folder filenames have no extension.** ERC-721A `tokenURI` returns `baseURI + tokenId` with no suffix, so the pinned metadata files must be named `1`, `2`, … (the CLI does this automatically — don't hand-roll the folder).
- **Token IDs start at 1, not 0.** The contract overrides `_startTokenId()`. Manifest index `0` → tokenId `1`.
- **`--mint` uses `ownerMint`, not `mint`.** It's free for the owner and doesn't require ETH. This is usually what you want for the initial drop.
- **State file `.nft-mint.json` sits next to the manifest.** It caches `imagesFolderCid`, `metadataFolderCid`, `baseUri`, and the deployed address. `deploy` / `drop --skip-upload` read from it. Safe to commit; delete to force a fresh upload.
- **Deploying on mainnet.** Warn the user explicitly. Prefer `cast wallet import` + `forge script --account` over a raw `PRIVATE_KEY` in `.env`. Run on Sepolia first.
- **Pinata quota.** Free tier is ~1 GB. If the user is dropping thousands of tokens, mention quota upfront.
- **`forge` not in `PATH`.** `drop` / `deploy` shell out to `forge script`. If it's missing, the command fails mid-flow *after* upload — ideally catch it in preflight (step 1).
- **Old Foundry + Etherscan V1 deprecated.** As of April 2026, Etherscan V1 API is deprecated; Foundry < ~0.3 (pre-late-2024 builds) still targets V1 and verification fails with "You are using a deprecated V1 endpoint". The CLI now treats verify-failure as a warning (the contract is still deployed + usable) — don't panic. Two fixes: (a) update Foundry (`foundryup`), or (b) re-run verification manually with the V2 URL:
  ```
  forge verify-contract <address> src/EternaImageNFT.sol:EternaImageNFT \
    --chain sepolia --verifier etherscan \
    --verifier-url "https://api.etherscan.io/v2/api?chainid=11155111" \
    --etherscan-api-key $ETHERSCAN_API_KEY
  ```
- **Never echo the full `forge` command.** It contains `--private-key <secret>`. The CLI masks this on failure; if you wrap it yourself, don't log the raw command line.

## If something breaks

- **Upload fails with 401/403** — `PINATA_JWT` wrong or missing `Files: Write` permission.
- **Upload fails with 413** — image too big. Pinata free tier caps individual files around 25 MB. Compress or split.
- **Deploy fails with "insufficient funds"** — wallet from `PRIVATE_KEY` needs Sepolia ETH. Use https://sepoliafaucet.com or Alchemy's faucet.
- **Deploy succeeds but verification fails** — non-fatal; re-run `nft-mint verify` separately, or pass `--no-verify` and skip Etherscan.
- **`tokenURI(1)` returns empty** — `baseURI` wasn't set at deploy time. Check `.nft-mint.json`; if `baseUri` is empty, run `upload` first or call `setBaseURI(string)` via `cast send <addr> "setBaseURI(string)" "ipfs://<cid>/"`.
- **NFT viewer shows metadata but no image** — IPFS gateway lag. Check `ipfs://<imagesCid>/1.png` via https://gateway.pinata.cloud/ipfs/ — if it loads, the viewer will catch up within ~10 min.
- **Testnet OpenSea links don't work.** OpenSea discontinued testnet support in 2025. Use the Etherscan `/nft/<contract>/<tokenId>` page on Sepolia — it has a built-in NFT viewer.

## What NOT to do

- Don't tell the user to open the Pinata dashboard and upload manually. The old README used to say this — the CLI now handles folder pinning automatically via the REST API.
- Don't compose `upload` + `deploy` + `batch-mint` as three separate commands unless they explicitly want that. `drop` exists specifically to collapse that.
- Don't use `forge create` directly. The repo has `script/Deploy.s.sol` and the CLI's `deploy` wires it up with the right env vars.
- Don't regenerate the manifest from scratch if they already have one. Edit the existing file.
- Don't commit `.env` or anything with a real `PRIVATE_KEY`.

## Quick reference

| Goal | Command |
| --- | --- |
| Fresh start | `nft-mint init <slug> --name "<name>" --count <n>` |
| Upload + deploy + mint | `nft-mint drop --manifest <slug>/manifest.json --network sepolia --mint` |
| Just pin to IPFS | `nft-mint upload --manifest <slug>/manifest.json` |
| Just deploy (uses saved baseURI) | `nft-mint deploy --manifest <slug>/manifest.json --network sepolia` |
| Mint all to a wallet | `nft-mint batch-mint --contract <addr> --manifest <slug>/manifest.json --network sepolia --owner` |
| Mint one to someone | `nft-mint mint --contract <addr> --to <addr> --quantity 1 --network sepolia` |
| Verify on Etherscan | `nft-mint verify --address <addr> --network sepolia --name … --symbol … --base-uri … --max-supply … --price … --owner …` |

The CLI binary is at `packages/cli/dist/index.js` when run from the repo root. A global `nft-mint` works if the user has run `pnpm link` or `npm i -g` — otherwise prefix with `node packages/cli/dist/index.js`.
