nft-standards について
ERC-721、ERC-1155、メタデータ、ロイヤリティなどを扱うNFT開発者向けの標準ガイドです。実装上の指針とSolidityの例を示し、カタログ版に参照ファイルが含まれない点も明記しています。
主な機能
- 01ERC-721とERC-1155のガイド
- 02メタデータ標準とパターン
- 03ロイヤリティとソウルバウンドの例
- 04ダイナミックNFTとミントのパターン
- 05検証と安全上の注意
活用シーン
- 01NFTコレクションの計画
- 02マーケットプレイス機能の実装
- 03トークンメタデータの設計
- 04ロイヤリティや譲渡不可の動作確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill nft-standardsインストール後、Claude Code で/nft-standardsを入力して読み込まれたか確認します。
詳細
サポート資料の提供状況
このカタログのコピーには、以下のインラインガイダンスが含まれていますが、次のサポート用パスは含まれていません。これらは外部の前提条件であり、同梱ファイルではありません。
<support-root>/references/erc721.md<support-root>/references/erc1155.md<support-root>/references/metadata-standards.md<support-root>/references/enumeration.md<support-root>/assets/erc721-contract.sol<support-root>/assets/erc1155-contract.sol<support-root>/assets/metadata-schema.json<support-root>/assets/metadata-uploader.py
これらのパスに依存する手順を実行する前に、元のサポート資料または既存のインストールを入手してください。このカタログには、検証済みのダウンロード元は記載されていません。各パスをそのインストールからの相対パスとして解決し、内容を確認したうえで、該当するライセンスと実行上の前提条件を検証してください。必要なファイルが存在するまでは、コマンドを実行したり、参照先の手順に従ったと主張したりしないでください。不足しているスクリプトを捏造したり、同じ名前の無関係なファイルを同等のものとして扱ったりしないでください。ここで完全に説明されている独立した手順は、ユーザーが許可した範囲内で実行できます。依存する手順は利用不可として報告してください。
NFT 規格
ERC-721 および ERC-1155 の NFT 規格、メタデータのベストプラクティス、高度な NFT 機能を習得します。
このスキルを使用しない場合
- タスクが NFT 規格に関係しない場合
- このスコープ外の別のドメインやツールが必要な場合
手順
- 目標、制約、必要な入力を明確にします。
- 関連するベストプラクティスを適用し、結果を検証します。
- 実行可能な手順と検証方法を提示します。
このスキルを使用する場合
- NFT コレクション(アート、ゲーム、コレクターズアイテム)の作成
- マーケットプレイス機能の実装
- オンチェーンまたはオフチェーンのメタデータの構築
- ソウルバウンドトークン(譲渡不可)の作成
- ロイヤリティと収益分配の実装
- 動的・進化型 NFT の開発
以下は、監査済みの本番用コントラクトではなく、OpenZeppelin 4.x の説明用レガシー断片です。レビュー済みの互換性のあるリリースを固定してコンパイルしてください。OpenZeppelin 5 では、フックと Ownable コンストラクターが異なります。不足しているヘルパー関数本体、メタデータ、コンストラクターは実装し、使い捨てのローカルチェーンでテストしてください。レビューによってデプロイ、ミント、資金移動、メタデータの公開が許可または暗示されるものではありません。
ERC-721(非代替性トークン規格)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
contract MyNFT is ERC721URIStorage, ERC721Enumerable, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
uint256 public constant MAX_SUPPLY = 10000;
uint256 public constant MINT_PRICE = 0.08 ether;
uint256 public constant MAX_PER_MINT = 20;
constructor() ERC721("MyNFT", "MNFT") {}
function mint(uint256 quantity) external payable {
require(quantity > 0 && quantity <= MAX_PER_MINT, "Invalid quantity");
require(_tokenIds.current() + quantity <= MAX_SUPPLY, "Exceeds max supply");
require(msg.value >= MINT_PRICE * quantity, "Insufficient payment");
// Reserve the entire batch before the first receiver callback.
uint256 firstId = _tokenIds.current() + 1;
for (uint256 i = 0; i < quantity; i++) { _tokenIds.increment(); }
for (uint256 i = 0; i < quantity; i++) {
uint256 newTokenId = firstId + i;
_safeMint(msg.sender, newTokenId);
_setTokenURI(newTokenId, generateTokenURI(newTokenId));
}
}
function generateTokenURI(uint256 tokenId) internal pure returns (string memory) {
// Return IPFS URI or on-chain metadata
return string(abi.encodePacked("ipfs://QmHash/", Strings.toString(tokenId), ".json"));
}
// Required overrides
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId,
uint256 batchSize
) internal override(ERC721, ERC721Enumerable) {
super._beforeTokenTransfer(from, to, tokenId, batchSize);
}
function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
super._burn(tokenId);
}
function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) {
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721URIStorage, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
function withdraw() external onlyOwner {
payable(owner()).transfer(address(this).balance);
}
}
ERC-1155(マルチトークン規格)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract GameItems is ERC1155, Ownable {
uint256 public constant SWORD = 1;
uint256 public constant SHIELD = 2;
uint256 public constant POTION = 3;
mapping(uint256 => uint256) public tokenSupply;
mapping(uint256 => uint256) public maxSupply;
constructor() ERC1155("ipfs://QmBaseHash/{id}.json") {
maxSupply[SWORD] = 1000;
maxSupply[SHIELD] = 500;
maxSupply[POTION] = 10000;
}
function mint(
address to,
uint256 id,
uint256 amount
) external onlyOwner {
require(tokenSupply[id] + amount <= maxSupply[id], "Exceeds max supply");
tokenSupply[id] += amount; // Effects before ERC-1155 receiver callback.
_mint(to, id, amount, "");
}
function mintBatch(
address to,
uint256[] memory ids,
uint256[] memory amounts
) external onlyOwner {
for (uint256 i = 0; i < ids.length; i++) {
require(tokenSupply[ids[i]] + amounts[i] <= maxSupply[ids[i]], "Exceeds max supply");
tokenSupply[ids[i]] += amounts[i];
}
_mintBatch(to, ids, amounts, "");
}
function burn(
address from,
uint256 id,
uint256 amount
) external {
require(from == msg.sender || isApprovedForAll(from, msg.sender), "Not authorized");
_burn(from, id, amount);
tokenSupply[id] -= amount;
}
}
メタデータ規格
オフチェーンメタデータ(IPFS)
{
"name": "NFT #1",
"description": "Description of the NFT",
"image": "ipfs://QmImageHash",
"attributes": [
{
"trait_type": "Background",
"value": "Blue"
},
{
"trait_type": "Rarity",
"value": "Legendary"
},
{
"trait_type": "Power",
"value": 95,
"display_type": "number",
"max_value": 100
}
]
}
オンチェーンメタデータ
contract OnChainNFT is ERC721 {
struct Traits {
uint8 background;
uint8 body;
uint8 head;
uint8 rarity;
}
mapping(uint256 => Traits) public tokenTraits;
function tokenURI(uint256 tokenId) public view override returns (string memory) {
Traits memory traits = tokenTraits[tokenId];
string memory json = Base64.encode(
bytes(
string(
abi.encodePacked(
'{"name": "NFT #', Strings.toString(tokenId), '",',
'"description": "On-chain NFT",',
'"image": "data:image/svg+xml;base64,', generateSVG(traits), '",',
'"attributes": [',
'{"trait_type": "Background", "value": "', Strings.toString(traits.background), '"},',
'{"trait_type": "Rarity", "value": "', getRarityName(traits.rarity), '"}',
']}'
)
)
)
);
return string(abi.encodePacked("data:application/json;base64,", json));
}
function generateSVG(Traits memory traits) internal pure returns (string memory) {
// Generate SVG based on traits
return "...";
}
}
ロイヤリティ(EIP-2981)
EIP-2981 はロイヤリティ情報を報告するものであり、マーケットプレイスにロイヤリティの支払いを強制するものではありません。支払いが確実であると約束するのではなく、現在のマーケットプレイスの動作を確認してください。この断片は、先ほど示したインポートに加えて IERC165 を前提としています。
import "@openzeppelin/contracts/interfaces/IERC2981.sol";
contract NFTWithRoyalties is ERC721, IERC2981, Ownable {
address public royaltyRecipient;
uint96 public royaltyFee = 500; // 5%
constructor() ERC721("Royalty NFT", "RNFT") {
royaltyRecipient = msg.sender;
}
function royaltyInfo(uint256 tokenId, uint256 salePrice)
external
view
override
returns (address receiver, uint256 royaltyAmount)
{
return (royaltyRecipient, (salePrice * royaltyFee) / 10000);
}
function setRoyalty(address recipient, uint96 fee) external onlyOwner {
require(fee <= 1000, "Royalty fee too high"); // Max 10%
royaltyRecipient = recipient;
royaltyFee = fee;
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, IERC165)
returns (bool)
{
return interfaceId == type(IERC2981).interfaceId ||
super.supportsInterface(interfaceId);
}
}
ソウルバウンドトークン(譲渡不可)
contract SoulboundToken is ERC721 {
uint256 private nextTokenId;
constructor() ERC721("Soulbound", "SBT") {}
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId,
uint256 batchSize
) internal virtual override {
require(from == address(0) || to == address(0), "Token is soulbound");
super._beforeTokenTransfer(from, to, tokenId, batchSize);
}
function mint(address to) external {
uint256 tokenId = ++nextTokenId; // Monotonic ID; burns never permit reuse.
_safeMint(to, tokenId);
}
// Burn is allowed (user can destroy their SBT)
function burn(uint256 tokenId) external {
require(ownerOf(tokenId) == msg.sender, "Not token owner");
_burn(tokenId);
}
}
動的 NFT
contract DynamicNFT is ERC721 {
struct TokenState {
uint256 level;
uint256 experience;
uint256 lastUpdated;
}
mapping(uint256 => TokenState) public tokenStates;
function gainExperience(uint256 tokenId, uint256 exp) external {
require(ownerOf(tokenId) == msg.sender, "Not token owner");
TokenState storage state = tokenStates[tokenId];
state.experience += exp;
// Level up logic
if (state.experience >= state.level * 100) {
state.level++;
}
state.lastUpdated = block.timestamp;
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
TokenState memory state = tokenStates[tokenId];
// Generate metadata based on current state
return generateMetadata(tokenId, state);
}
function generateMetadata(uint256 tokenId, TokenState memory state)
internal
pure
returns (string memory)
{
// Dynamic metadata generation
return "";
}
}
ガス最適化ミント(ERC721A)
import "erc721a/contracts/ERC721A.sol";
contract OptimizedNFT is ERC721A {
uint256 public constant MAX_SUPPLY = 10000;
uint256 public constant MINT_PRICE = 0.05 ether;
constructor() ERC721A("Optimized NFT", "ONFT") {}
function mint(uint256 quantity) external payable {
require(_totalMinted() + quantity <= MAX_SUPPLY, "Exceeds max supply");
require(msg.value >= MINT_PRICE * quantity, "Insufficient payment");
_mint(msg.sender, quantity);
}
function _baseURI() internal pure override returns (string memory) {
return "ipfs://QmBaseHash/";
}
}
リソース
- references/erc721.md:ERC-721 仕様の詳細
- references/erc1155.md:ERC-1155 マルチトークン規格
- references/metadata-standards.md:メタデータのベストプラクティス
- references/enumeration.md:トークン列挙のパターン
- assets/erc721-contract.sol:本番用 ERC-721 テンプレート
- assets/erc1155-contract.sol:本番用 ERC-1155 テンプレート
- assets/metadata-schema.json:標準メタデータ形式
- assets/metadata-uploader.py:IPFS アップロードユーティリティ
ベストプラクティス
- OpenZeppelin を使用する:十分に実績のある実装を利用します
- メタデータを固定する:ピンニングサービスを利用して IPFS に保存します
- ロイヤリティを実装する:マーケットプレイスとの互換性のために EIP-2981 を使用します
- ガスを最適化する:バッチミントには ERC721A を使用します
- リビールの仕組み:プレースホルダーから公開するパターンを採用します
- 列挙:マーケットプレイス向けに walletOfOwner をサポートします
- ホワイトリスト:効率的なホワイトリスト作成にマークルツリーを使用します
マーケットプレイスとの連携
- OpenSea:ERC-721/1155、メタデータ標準
- マーケットプレイスのロイヤリティの挙動は、ポリシーやバージョンによって異なります。主張する前に確認してください。
- Rarible:プロトコル手数料、遅延ミント
- Blur:ガスを最適化した取引
制限事項
- 上記の説明にある範囲にタスクが明確に該当する場合に限り、このスキルを使用してください。
- 出力を、環境固有の検証、テスト、または専門家によるレビューの代わりとして扱わないでください。
- 必要な入力、権限、安全上の境界、または成功基準が不足している場合は、作業を中断して確認を求めてください。