Back to Backend Development

nft-standards

NFTERC-721ERC-1155BlockchainSoliditySmart ContractsDigital AssetsWeb3
36.8k📄 MIT🕒 2026-06-16Source ↗

Install this skill

npx skills add wshobson/agents

Works across Claude Code, Cursor, Codex, Copilot & Antigravity

The nft-standards skill manages the implementation of Ethereum-based non-fungible token contracts. It focuses on the technical architecture of ERC-721 for singular, unique assets and ERC-1155 for multi-token systems. This skill provides structure for handling minting logic, supply constraints, and metadata connectivity through IPFS or on-chain storage. Developers using this skill can implement custom transfer overrides, ownership access controls, and batch transaction capabilities required for modern digital asset deployments. By relying on established OpenZeppelin libraries, the skill ensures adherence to standardized interface identification and interaction patterns, allowing for integration with marketplaces and external wallet viewers. It bridges the gap between raw Solidity development and functional, standards-compliant digital assets suitable for decentralized applications, gaming economies, and digital collectibles.

When to Use This Skill

  • Building generative art collections with distinct traits
  • Developing in-game item systems with varying supply quantities
  • Establishing royalty-compliant token architectures
  • Developing soulbound tokens with transfer restrictions

How to Invoke This Skill

Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:

  • write an ERC-721 contract for a new NFT project
  • create a multi-token ERC-1155 contract for game assets
  • implement metadata URI storage for an NFT collection
  • add minting limits and price checks to my NFT contract
  • refactor my token contract to support batch minting

Pro Tips

  • 💡Always prioritize security audits for your NFT contracts, especially when handling user funds or complex logic. Leverage established libraries like OpenZeppelin.
  • 💡Design your metadata schema carefully. Consider off-chain storage solutions like IPFS for large assets to keep gas costs low, but ensure immutability and proper linking.
  • 💡Plan for scalability. If expecting high mint volumes, optimize your `mint` function and consider layer-2 solutions or gas-efficient strategies for broader adoption.

What this skill does

  • Implementation of ERC-721 single-token logic with metadata management
  • Deployment of ERC-1155 semi-fungible and multi-token architecture
  • Configuration of minting constraints, supply caps, and price enforcement
  • Integration of metadata schemas using IPFS URIs or on-chain encoding
  • Management of ownership, burning, and administrative withdrawal functions

When not to use it

  • Creating simple fungible currency or governance tokens requiring ERC-20
  • Complex DeFi applications requiring sophisticated state machines outside token standards

Example workflow

  1. Define the token architecture as either ERC-721 or ERC-1155 based on asset scarcity requirements.
  2. Implement the base contract using OpenZeppelin standards to ensure interface compliance.
  3. Define custom minting functions with supply caps and payment requirements.
  4. Construct the metadata JSON schema including trait and attribute fields.
  5. Configure the URI storage pointers to point to IPFS or on-chain logic.
  6. Perform final testing of transfer, burn, and ownership authority methods.

Prerequisites

  • Solidity programming knowledge
  • Understanding of OpenZeppelin library syntax
  • Ethereum development environment setup

Pitfalls & limitations

  • !Incorrect override patterns leading to interface support failure
  • !Hardcoded metadata URIs that cannot be updated after minting
  • !Gas inefficiency during high-volume batch minting operations

FAQ

Should I use ERC-721 or ERC-1155 for my project?
Use ERC-721 if each item is entirely unique, like digital art. Use ERC-1155 if you need to manage multiple item types in a single contract, such as game items.
Can I update the metadata after an NFT is minted?
Only if the contract specifically includes a function to update the URI for specific token IDs, which is not standard in basic implementations.
Why is OpenZeppelin used for these contracts?
It provides community-audited, secure implementations that handle edge cases like interface checks and safe transfers, reducing the surface area for bugs.

How it compares

Using this skill enforces industry-standard interfaces that ensure your tokens appear correctly on marketplaces, whereas manual implementation often misses crucial override requirements for secondary platform support.

Source & trust

37k stars📄 MIT🕒 Updated 2026-06-16
📄 Full skill instructions — original source: wshobson/agents
# 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)

// 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)

// 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)

{
"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

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)

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)

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

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)

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

How to Use This Skill Unit

Option A: Project-Specific (Recommended)

  1. Click "Download" above
  2. In your project, create the directory: .agent/skills/nft-standards/
  3. Save the file as SKILL.md
  4. The agent will automatically discover the skill based on its description.

Option B: Global Installation (All Agents)

Save the file to these locations to make it available across all projects:

  • Claude Code: ~/.claude/skills/wshobson/agents/nft-standards/SKILL.md
  • Cursor: ~/.cursor/skills/wshobson/agents/nft-standards/SKILL.md
  • Antigravity: ~/.gemini/antigravity/skills/wshobson/agents/nft-standards/SKILL.md

🚀 Install with CLI:
npx skills add wshobson/agents

Read the Master Guide: Mastering Agent Skills

Recommended Rules

View more rules

Recommended Workflows

View more workflows

Recommended MCP Servers

View more MCP servers

Take It Further

Maximize your productivity with these powerful resources

📋

Define Your Standards

Set up coding standards to ensure this workflow produces consistent, high-quality results.

Browse Rules Library
📖

Master Workflows

Learn how to create custom workflows, use Turbo Mode, and build your automation library.

Complete Guide

How to use this Skill in Claude Code & Cursor

For Claude Code (CLI)

To use this skill in Claude Code, copy the rule content into your project's custom instructions or follow our Add-Skill CLI guide. This ensures Claude follows your standards during every code generation.

For Cursor & Windsurf

For Cursor or Windsurf, individual skills are best used in the "Rules for AI" section. This specific unit helps the agent avoid backend development issues, leading to cleaner, more efficient code.

Why the skill format matters: the standardized Agent Skills format lets your AI agent load detailed instructions only when they are relevant, keeping your prompt clean while improving results.

Source & attribution

This skill is categorized under Backend Development and is published by W. Shobson, maintained in wshobson/agents.

← Browse All Agent Skills
Sponsored AI assistant. Recommendations may be paid.