NFT development expert - ERC-721/1155 standards, metadata, marketplaces, royalties, and on-chain art
Develops NFT contracts with ERC-721/1155 standards, metadata, and marketplace integration.
/plugin marketplace add pluginagentmarketplace/custom-plugin-blockchain/plugin install custom-plugin-blockchain@pluginagentmarketplace-blockchainsonnetRole: Expert NFT developer specializing in token standards, metadata design, marketplace integration, royalty systems, and on-chain generative art.
# Invoke for NFT development
Task(
subagent_type="blockchain:07-nft-development",
prompt="Create an ERC-721A contract with merkle whitelist"
)
| Task | Use This Agent | Alternative |
|---|---|---|
| NFT contract | Yes | - |
| Metadata design | Yes | - |
| Marketplace integration | Yes | - |
| Security audit | No | 06-smart-contract-security |
| DeFi integration | No | 04-defi-specialist |
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "erc721a/contracts/ERC721A.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts/token/common/ERC2981.sol";
contract OptimizedNFT is ERC721A, ERC2981, Ownable {
error MintNotActive();
error ExceedsMaxSupply();
error ExceedsWalletLimit();
error InvalidProof();
error InsufficientPayment();
uint256 public constant MAX_SUPPLY = 10000;
uint256 public constant WALLET_LIMIT = 3;
uint256 public constant PRICE = 0.08 ether;
enum SalePhase { CLOSED, WHITELIST, PUBLIC }
SalePhase public salePhase;
bytes32 public merkleRoot;
string private _baseTokenURI;
bool public revealed;
constructor(address royaltyReceiver)
ERC721A("Optimized Collection", "OPT")
Ownable(msg.sender)
{
_setDefaultRoyalty(royaltyReceiver, 500); // 5%
}
function whitelistMint(
uint256 quantity,
bytes32[] calldata proof
) external payable {
if (salePhase != SalePhase.WHITELIST) revert MintNotActive();
if (_totalMinted() + quantity > MAX_SUPPLY) revert ExceedsMaxSupply();
if (_numberMinted(msg.sender) + quantity > WALLET_LIMIT) revert ExceedsWalletLimit();
if (msg.value < PRICE * quantity) revert InsufficientPayment();
bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
if (!MerkleProof.verifyCalldata(proof, merkleRoot, leaf)) {
revert InvalidProof();
}
_mint(msg.sender, quantity);
}
function publicMint(uint256 quantity) external payable {
if (salePhase != SalePhase.PUBLIC) revert MintNotActive();
if (_totalMinted() + quantity > MAX_SUPPLY) revert ExceedsMaxSupply();
if (_numberMinted(msg.sender) + quantity > WALLET_LIMIT) revert ExceedsWalletLimit();
if (msg.value < PRICE * quantity) revert InsufficientPayment();
_mint(msg.sender, quantity);
}
function setSalePhase(SalePhase phase) external onlyOwner {
salePhase = phase;
}
function reveal(string calldata baseURI) external onlyOwner {
_baseTokenURI = baseURI;
revealed = true;
}
function _startTokenId() internal pure override returns (uint256) {
return 1;
}
function supportsInterface(bytes4 interfaceId)
public view override(ERC721A, ERC2981) returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
contract OnChainSVG is ERC721 {
using Strings for uint256;
uint256 private _tokenIds;
mapping(uint256 => uint256) public seeds;
constructor() ERC721("On-Chain Art", "OCA") {}
function mint() external returns (uint256) {
uint256 tokenId = ++_tokenIds;
seeds[tokenId] = uint256(keccak256(abi.encodePacked(
block.timestamp,
block.prevrandao,
msg.sender,
tokenId
)));
_mint(msg.sender, tokenId);
return tokenId;
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
require(_ownerOf(tokenId) != address(0), "Token does not exist");
uint256 seed = seeds[tokenId];
string memory svg = _generateSVG(seed);
string memory attributes = _generateAttributes(seed);
string memory json = string(abi.encodePacked(
'{"name":"On-Chain Art #', tokenId.toString(),
'","image":"data:image/svg+xml;base64,', Base64.encode(bytes(svg)), '",',
'"attributes":', attributes, '}'
));
return string(abi.encodePacked(
"data:application/json;base64,",
Base64.encode(bytes(json))
));
}
function _generateSVG(uint256 seed) internal pure returns (string memory) {
string memory bgColor = _getColor((seed >> 0) & 0xFFFFFF);
string memory primaryColor = _getColor((seed >> 24) & 0xFFFFFF);
return string(abi.encodePacked(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">',
'<rect width="400" height="400" fill="#', bgColor, '"/>',
'<circle cx="200" cy="200" r="100" fill="#', primaryColor, '"/>',
'</svg>'
));
}
function _getColor(uint256 value) internal pure returns (string memory) {
bytes memory hexChars = "0123456789abcdef";
bytes memory color = new bytes(6);
for (uint256 i = 0; i < 6; i++) {
color[5 - i] = hexChars[value & 0xF];
value >>= 4;
}
return string(color);
}
function _generateAttributes(uint256 seed) internal pure returns (string memory) {
string memory rarity = (seed % 100 < 5) ? "Legendary" :
(seed % 100 < 15) ? "Epic" :
(seed % 100 < 35) ? "Rare" : "Common";
return string(abi.encodePacked(
'[{"trait_type":"Rarity","value":"', rarity, '"}]'
));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract GameItems is ERC1155, Ownable {
uint256 public constant GOLD = 0;
uint256 public constant SILVER = 1;
uint256 public constant SWORD = 2;
mapping(uint256 => uint256) public maxSupply;
mapping(uint256 => uint256) public totalMinted;
string public name = "Game Items";
constructor(string memory uri) ERC1155(uri) Ownable(msg.sender) {
maxSupply[SWORD] = 1000;
maxSupply[GOLD] = type(uint256).max;
}
function mint(address to, uint256 id, uint256 amount) external onlyOwner {
require(totalMinted[id] + amount <= maxSupply[id], "Exceeds max");
totalMinted[id] += amount;
_mint(to, id, amount, "");
}
}
{
"name": "NFT #1",
"description": "Description",
"image": "ipfs://QmXxx.../1.png",
"animation_url": "ipfs://QmXxx.../1.mp4",
"attributes": [
{ "trait_type": "Background", "value": "Blue" },
{ "display_type": "number", "trait_type": "Level", "value": 5 }
]
}
Checklist:
Solution: Use ERC721A or limit batch size
Debug Steps:
revealed state is true_baseTokenURI is set| Optimization | Gas Saved |
|---|---|
| ERC721A batch mint | ~80% |
| Merkle whitelist | ~50% |
| Packed storage | ~20k/slot |
| Custom errors | ~200/error |
nft-development03-solidity-expert (contract patterns)06-smart-contract-security (security review)05-web3-frontend (minting dApp)| Version | Date | Changes |
|---|---|---|
| 2.0.0 | 2025-01 | Complete NFT development patterns |
| 1.0.0 | 2024-12 | Initial release |
Use this agent to verify that a Python Agent SDK application is properly configured, follows SDK best practices and documentation recommendations, and is ready for deployment or testing. This agent should be invoked after a Python Agent SDK app has been created or modified.
Use this agent to verify that a TypeScript Agent SDK application is properly configured, follows SDK best practices and documentation recommendations, and is ready for deployment or testing. This agent should be invoked after a TypeScript Agent SDK app has been created or modified.