solidity-nft
solidity-nft: NFT 開発スキル
NFT 開発における トークン標準選択、マーケットプレイス設計、メタデータ戦略を提供するドメイン特化スキル。
対象
- ERC721 / ERC1155 / ERC6551(Token Bound Accounts)
- NFT マーケットプレイス(オーダーブック・オークション)
- メタデータ管理(オンチェーン・IPFS・動的 NFT)
- ロイヤリティ(EIP-2981)
ワークフロー
Step 1: 要件に応じたリファレンス選択
ユーザーの要件から NFT の対象ドメインを判定する:
| ユースケース | リファレンス | 読み込み時の注目ポイント |
|---|---|---|
| トークン発行・ミント | references/token-standards.md |
ERC721 vs ERC1155 の選択基準、ERC721A のガス最適化、ERC6551 の TBA パターン |
| マーケットプレイス構築 | references/marketplace-patterns.md |
オーダーブック vs オークション、エスクロー、ロイヤリティ徴収 |
| メタデータ設計 | references/metadata-patterns.md |
オンチェーン vs IPFS vs 動的 NFT、OpenSea 互換メタデータスキーマ |
複数ドメインが関連する場合(例: ミント + メタデータ)は全ての該当リファレンスを読み込む。
検証ゲート: 少なくとも 1 つのドメインが特定できること。要件が不明確な場合は AskUserQuestion で確認する。
Step 2: トークン標準の決定
要件に基づきトークン標準を選択する:
| 要件 | 推奨標準 | 理由 |
|---|---|---|
| 1:1 のユニークアイテム | ERC721 | 最も広くサポートされる。OpenSea / マーケットプレイス互換性が高い |
| 大量ミント(1000+) | ERC721A | ERC721 互換。バッチミントでガス 60-70% 削減 |
| 同一アイテムの複数発行 | ERC1155 | FT と NFT を同一コントラクトで管理。ゲームアイテムに最適 |
| ゲームアイテム(複数種類) | ERC1155 | balanceOf(account, tokenId) で種類別の保有数を効率的に管理 |
| NFT にウォレット機能を持たせたい | ERC6551 | NFT 自体がスマートアカウントを持ち、資産の保有・トランザクション実行が可能 |
判断が難しい場合: AskUserQuestion で「アイテムの種類数」「同一アイテムの発行数」「ガスコスト重視か機能重視か」を確認する。
検証ゲート: トークン標準が決定し、対応する OpenZeppelin / Solady / ERC721A ライブラリが利用可能であること。
Step 3: コード生成・レビュー
solidity-coreのlanguage-patterns.mdに従い NatSpec・コーディング規約を適用する。- NFT 固有の考慮事項を組み込む:
- ミント関数には必ずアクセス制御(
onlyOwner/ Merkle proof / 署名検証) MAX_SUPPLY上限の設定- メタデータ URI の設計(
baseURI+tokenId、または個別 URI) - ロイヤリティ: EIP-2981 の
royaltyInfo実装
- ミント関数には必ずアクセス制御(
- テストコードを同時に生成する:
- ミント(成功・上限超過・権限不足)
- transfer・approve
- メタデータ URI の正当性
- ロイヤリティ計算の正確性
検証ゲート: forge build がエラーなく完了すること。forge test で全テストがパスすること。
Step 4: メタデータ・マーケットプレイス確認
- メタデータ: OpenSea / マーケットプレイスの標準に準拠しているか:
name,description,imageフィールドが存在するかattributesが trait 表示に対応しているかtokenURIが有効な JSON を返すか
- ロイヤリティ: EIP-2981 の
royaltyInfoが正しいアドレスと金額を返すか - 列挙:
ERC721Enumerableが必要な場合はガスコストのトレードオフを理解した上で導入しているか
検証ゲート: tokenURI が有効な JSON を返し、OpenSea メタデータ標準に準拠していること。
使用例
例 1: ジェネラティブ NFT コレクション
ユーザー入力: 「10,000 体の NFT コレクションを作りたい。ホワイトリスト付きでミントしたい」
アクション:
- Step 1: トークン発行 + メタデータ →
token-standards.md+metadata-patterns.mdを読み込み - Step 2: 大量ミント → ERC721A を選択(バッチミントでガス削減)
- Step 3: 以下を生成:
src/GenesisNFT.sol— ERC721A 継承。Merkle proof によるホワイトリスト検証、MAX_SUPPLY = 10_000、reveal 機能(初期は placeholder URI → 後で baseURI を更新)src/libraries/MerkleVerifier.sol— Merkle proof 検証ヘルパーtest/GenesisNFT.t.sol— ホワイトリスト検証、ミント上限、reveal 前後の URI テストscript/GenerateMerkleRoot.s.sol— ホワイトリストからの Merkle root 生成スクリプト
- Step 4: メタデータが OpenSea 標準に準拠しているか確認。ロイヤリティ設定(5% を推奨)
結果: Merkle proof ホワイトリスト付きの ERC721A コレクションがテスト・スクリプト付きで生成される。
例 2: 動的 NFT(ゲーム内レベルアップ)
ユーザー入力: 「NFT のレベルが上がるとメタデータが変わるようにしたい」
アクション:
- Step 1: メタデータ設計 →
metadata-patterns.mdを読み込み → 動的 NFT パターンを選択 - Step 2: 1:1 ユニーク → ERC721 を選択
- Step 3: 以下を生成:
src/DynamicNFT.sol— ERC721URIStorage 継承。levelmapping、levelUp関数、tokenURIをオンチェーンで動的生成(Base64 エンコード JSON + SVG)test/DynamicNFT.t.sol— レベルアップ前後の tokenURI 変化テスト、権限チェック
- Step 4:
tokenURIがレベルに応じて正しい JSON を返すか確認
結果: レベルに応じてメタデータが自動更新されるオンチェーン NFT が生成される。
例 3: ERC1155 ゲームアイテム
ユーザー入力: 「ゲームのアイテム(剣・盾・ポーション)を NFT として発行したい。ポーションは消費可能にして」
アクション:
- Step 1: 複数種類 + 消費 →
token-standards.mdの ERC1155 セクションを読み込み - Step 2: 複数種類のアイテム → ERC1155 を選択
- Step 3: 以下を生成:
src/GameItems.sol— ERC1155 継承。アイテム ID 定数(SWORD = 0,SHIELD = 1,POTION = 2)。mint/mintBatch、usePotion関数(burnで消費)test/GameItems.t.sol— ミント、バッチ転送、ポーション消費、残高確認テスト
- Step 4:
uri関数が各アイテム ID に対して正しいメタデータを返すか確認
結果: FT(ポーション:消費可能)と NFT(剣・盾:ユニーク)を同一コントラクトで管理するゲームアイテムが生成される。
トラブルシューティング
1. OpenSea でメタデータが表示されない
症状: NFT が OpenSea に表示されるが、画像・属性が出ない
原因と対策:
tokenURIが無効な JSON を返す:cast call <address> "tokenURI(uint256)" <tokenId>で返り値を確認する。JSON が正しい形式か検証する。- IPFS ゲートウェイの問題:
ipfs://プレフィックスを使用しているか確認。HTTPS ゲートウェイ URL(https://ipfs.io/ipfs/...)でもアクセス可能にする。 - メタデータのキャッシュ: OpenSea は メタデータをキャッシュする。更新後は OpenSea API の
refresh_metadataを呼び出す。 imageフィールドの MIME タイプ: SVG の場合はdata:image/svg+xml;base64,...形式を使用する。
2. 大量ミント時のガスコストが高い
症状: 1件ミントごとに 100k+ gas かかる
原因と対策:
- ERC721 の
_safeMintを使用: ERC721A に切り替えることで、バッチミント時のガスを 60-70% 削減できる。forge install chiru-labs/ERC721Aでインストール。 ERC721Enumerableの使用: Enumerable は追加のストレージ書き込みでガスが高い。不要な場合は削除する。totalSupplyは counter 変数で代替可能。- ストレージの最適化:
uint256の tokenId カウンターをuint96等に縮小し、struct 内でパッキングする。
3. ミントが MaxSupplyReached でリバートする
症状: まだ MAX_SUPPLY に達していないのにリバートする
原因と対策:
- カウンターの不一致:
_tokenIdCounterが 0 始まりか 1 始まりかを確認する。MAX_SUPPLY = 10_000で 0 始まりの場合、最後の tokenId は 9999。 - バッチミント時の上限チェック:
_tokenIdCounter + quantity > MAX_SUPPLYではなく_tokenIdCounter + quantity <= MAX_SUPPLYで判定しているか確認する。
4. ロイヤリティが マーケットプレイスで適用されない
症状: EIP-2981 を実装したが、一部のマーケットプレイスでロイヤリティが徴収されない
原因と対策:
- EIP-2981 は任意準拠: EIP-2981 はオフチェーンの照会標準であり、オンチェーンで強制はされない。マーケットプレイスが EIP-2981 をサポートしているか確認する。
- Operator Filter(非推奨): OpenSea の OperatorFilterRegistry は 2023 年に非推奨化された。新規プロジェクトでは EIP-2981 のみ実装する。
- ロイヤリティ率の確認:
royaltyInfo(tokenId, salePrice)が正しい金額を返すかcast callで確認する。推奨率は 2.5-7.5%。
5. ERC6551 Token Bound Account のデプロイが失敗する
症状: NFT に紐づくアカウントの作成が失敗する
原因と対策:
- Registry アドレスの確認: ERC6551 Registry は各チェーンに公式デプロイされている。正しいアドレスを使用しているか確認する。
- implementation アドレスの確認:
createAccountに渡す implementation コントラクトが正しくデプロイされているか確認する。 - salt の衝突: 同じ NFT + salt で既にアカウントが存在する場合は
account()で既存アドレスを取得する。
注意事項
- ミント関数には必ず適切なアクセス制御を設定する(public mint でも
MAX_PER_TX/MAX_PER_WALLETの制限を推奨)。 - 大量ミント時のガスコストを考慮する(ERC721A 等の最適化実装の検討)。
- メタデータの永続性を確保する(IPFS pinning / Arweave)。CID(Content Identifier)は不変であるため、メタデータ変更には新しい CID が必要。
- ロイヤリティは EIP-2981 を実装し、マーケットプレイスでの強制力の限界を理解する。
- 基盤的なパターン(アクセス制御、ガス最適化等)は
solidity-coreを参照する。 - フロントエンド統合(ミント UI、ギャラリー表示)は
web3-frontendを参照する。 - OpenZeppelin コントラクトの利用を推奨し、独自実装は避ける。