| name | nft-standards |
| description | 適切なメタデータ処理、ミント戦略、マーケットプレイス統合を備えたNFT標準(ERC-721、ERC-1155)を実装します。NFTコントラクトを作成する時、NFTマーケットプレイスを構築する時、またはデジタル資産システムを実装する時に使用してください。 |
English | 日本語
NFT標準
ERC-721とERC-1155 NFT標準、メタデータのベストプラクティス、高度なNFT機能をマスターします。
このスキルを使用するタイミング
- NFTコレクション(アート、ゲーム、コレクティブル)を作成する
- マーケットプレイス機能を実装する
- オンチェーンまたはオフチェーンメタデータを構築する
- ソウルバウンドトークン(譲渡不可)を作成する
- ロイヤリティと収益分配を実装する
- 動的/進化するNFTを開発する
ERC-721(Non-Fungible Token Standard)
// 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";
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");
for (uint256 i = 0; i < quantity; i++) {
_tokenIds.increment();
uint256 newTokenId = _tokenIds.current();
_safeMint(msg.sender, newTokenId);
_setTokenURI(newTokenId, generateTokenURI(newTokenId));
}
}
function generateTokenURI(uint256 tokenId) internal pure returns (string memory) {
// IPFS URIまたはオンチェーンメタデータを返す
return string(abi.encodePacked("ipfs://QmHash/", Strings.toString(tokenId), ".json"));
}
// 必要なオーバーライド
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(ERC721, 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");
_mint(to, id, amount, "");
tokenSupply[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) {
// 特性に基づいてSVGを生成
return "...";
}
}
ロイヤリティ(EIP-2981)
import "@openzeppelin/contracts/interfaces/IERC2981.sol";
contract NFTWithRoyalties is ERC721, IERC2981 {
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"); // 最大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 {
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 = totalSupply() + 1;
_safeMint(to, tokenId);
}
// バーンは許可(ユーザーは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;
// レベルアップロジック
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];
// 現在の状態に基づいてメタデータを生成
return generateMetadata(tokenId, state);
}
function generateMetadata(uint256 tokenId, TokenState memory state)
internal
pure
returns (string memory)
{
// 動的メタデータ生成
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、メタデータ標準
- LooksRare: ロイヤリティ強制
- Rarible: プロトコル手数料、レイジーミント
- Blur: ガス最適化取引