nft-standards

ERC-721 and ERC-1155 NFT standards with metadata, royalties, and advanced features. Covers both ERC-721 (unique tokens) and ERC-1155 (multi-token) standards with complete contract examples including minting, burning, and supply management Supports off-chain metadata via IPFS and on-chain metadata with SVG generation, plus EIP-2981 royalty implementation for marketplace compatibility Includes soulbound token patterns (non-transferable), dynamic NFTs with evolving state, and gas-optimized minting via ERC721A Provides metadata JSON schema with trait definitions, display types, and rarity attributes for marketplace integration

INSTALLATION
npx skills add https://github.com/wshobson/agents --skill nft-standards
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

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 &#x26;&#x26; 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/";

}

}

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card