Initial commit
This commit is contained in:
381
skills/nft-standards/SKILL.md
Normal file
381
skills/nft-standards/SKILL.md
Normal file
@@ -0,0 +1,381 @@
|
||||
---
|
||||
name: nft-standards
|
||||
description: Implement NFT standards (ERC-721, ERC-1155) with proper metadata handling, minting strategies, and marketplace integration. Use when creating NFT contracts, building NFT marketplaces, or implementing digital asset systems.
|
||||
---
|
||||
|
||||
# NFT Standards
|
||||
|
||||
Master ERC-721 and ERC-1155 NFT standards, metadata best practices, and advanced NFT features.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Creating NFT collections (art, gaming, collectibles)
|
||||
- Implementing marketplace functionality
|
||||
- Building on-chain or off-chain metadata
|
||||
- Creating soulbound tokens (non-transferable)
|
||||
- Implementing royalties and revenue sharing
|
||||
- Developing dynamic/evolving NFTs
|
||||
|
||||
## ERC-721 (Non-Fungible Token Standard)
|
||||
|
||||
```solidity
|
||||
// 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) {
|
||||
// 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(ERC721, ERC721Enumerable)
|
||||
returns (bool)
|
||||
{
|
||||
return super.supportsInterface(interfaceId);
|
||||
}
|
||||
|
||||
function withdraw() external onlyOwner {
|
||||
payable(owner()).transfer(address(this).balance);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ERC-1155 (Multi-Token Standard)
|
||||
|
||||
```solidity
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Metadata Standards
|
||||
|
||||
### Off-Chain Metadata (IPFS)
|
||||
```json
|
||||
{
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### On-Chain Metadata
|
||||
```solidity
|
||||
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 "...";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Royalties (EIP-2981)
|
||||
|
||||
```solidity
|
||||
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"); // 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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Soulbound Tokens (Non-Transferable)
|
||||
|
||||
```solidity
|
||||
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);
|
||||
}
|
||||
|
||||
// Burn is allowed (user can destroy their SBT)
|
||||
function burn(uint256 tokenId) external {
|
||||
require(ownerOf(tokenId) == msg.sender, "Not token owner");
|
||||
_burn(tokenId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dynamic NFTs
|
||||
|
||||
```solidity
|
||||
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 "";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Gas-Optimized Minting (ERC721A)
|
||||
|
||||
```solidity
|
||||
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/";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- **references/erc721.md**: ERC-721 specification details
|
||||
- **references/erc1155.md**: ERC-1155 multi-token standard
|
||||
- **references/metadata-standards.md**: Metadata best practices
|
||||
- **references/enumeration.md**: Token enumeration patterns
|
||||
- **assets/erc721-contract.sol**: Production ERC-721 template
|
||||
- **assets/erc1155-contract.sol**: Production ERC-1155 template
|
||||
- **assets/metadata-schema.json**: Standard metadata format
|
||||
- **assets/metadata-uploader.py**: IPFS upload utility
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use OpenZeppelin**: Battle-tested implementations
|
||||
2. **Pin Metadata**: Use IPFS with pinning service
|
||||
3. **Implement Royalties**: EIP-2981 for marketplace compatibility
|
||||
4. **Gas Optimization**: Use ERC721A for batch minting
|
||||
5. **Reveal Mechanism**: Placeholder → reveal pattern
|
||||
6. **Enumeration**: Support walletOfOwner for marketplaces
|
||||
7. **Whitelist**: Merkle trees for efficient whitelisting
|
||||
|
||||
## Marketplace Integration
|
||||
|
||||
- OpenSea: ERC-721/1155, metadata standards
|
||||
- LooksRare: Royalty enforcement
|
||||
- Rarible: Protocol fees, lazy minting
|
||||
- Blur: Gas-optimized trading
|
||||
Reference in New Issue
Block a user