🎨 NFT Foundations
🎯 What are NFTs?
Non-Fungible Tokens (NFTs) are unique digital assets on a blockchain that represent ownership of a specific item — digital art, collectibles, virtual real estate, or other unique items.
Key Concepts
Fungibility: Unlike cryptocurrencies (fungible), NFTs are unique and cannot be exchanged one-for-one. Blockchain: NFTs are secured on Ethereum, Solana, and other chains. Ownership: Proof of ownership is stored on-chain.
// Example: Simple NFT ownership structure
struct NFT {
uint256 tokenId;
address owner;
string metadataURI;
bool isTransferable;
}
// Minting a new NFT
function mintNFT(address to, string memory uri) public returns (uint256) {
uint256 tokenId = _tokenIds.current();
_tokenIds.increment();
_mint(to, tokenId);
_setTokenURI(tokenId, uri);
return tokenId;
}📜 ERC-721 & ERC-1155
🎯 Token Standards
ERC-721 is the standard for non-fungible tokens. ERC-1155 supports both fungible and non-fungible tokens in one contract.
Standard Comparison
ERC-721: Each token is unique, separate transfer operations per token. ERC-1155: Batch transfers, semi-fungible tokens, efficient for gaming.
import openzepplin
// ERC-721 Implementation
contract MyNFT is ERC721 {
constructor() ERC721("MyNFT", "MNFT") {}
function safeMint(address to, uint256 tokenId) public {
_safeMint(to, tokenId);
}
}
// ERC-1155 Implementation
contract MyCollection is ERC1155 {
constructor() ERC1155("https://api.example.com/token/{id}.json") {}
function mint(address to, uint256 id, uint256 amount) public {
_mint(to, id, amount, "");
}
}📋 Metadata Standards
🎯 On-Chain & Off-Chain Metadata
Metadata describes the NFT's properties, attributes, and content. It can be stored on-chain or off-chain (IPFS, centralized servers).
Metadata Structure
Standard fields: name, description, image, attributes. Dynamic attributes: Trait types, levels, stats. Storage: IPFS recommended for decentralization.
{
"name": "CryptoKitty #1234",
"description": "A cute digital cat with unique traits.",
"image": "ipfs://QmX...",
"attributes": [
{"trait_type": "Fur", "value": "Orange"},
{"trait_type": "Eyes", "value": "Blue"},
{"trait_type": "Rarity", "value": "Legendary"}
],
"external_url": "https://cryptokitties.co/1234"
}🔧 Smart Contract Development
🎯 Writing NFT Contracts
Smart contracts are the backbone of NFTs — they handle minting, transferring, burning, and implementing business logic.
Contract Architecture
Inheritance: Use OpenZeppelin contracts. Access control: Ownable, roles, modifiers. Events: Transfer, Approval events for indexing.
// Complete NFT contract
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyNFT is ERC721, Ownable {
uint256 private _tokenIdCounter;
mapping(uint256 => string) private _tokenURIs;
constructor() ERC721("MyNFT", "MNFT") {}
function mint(address to, string memory uri) public onlyOwner {
uint256 tokenId = _tokenIdCounter;
_tokenIdCounter++;
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
}
}🏪 Marketplace Mechanics
🎯 Buying, Selling, Trading
NFT marketplaces facilitate peer-to-peer trading of NFTs with listing, bidding, and escrow mechanisms.
Marketplace Features
Listings: Fixed price or auction. Bidding: Highest bid wins. Royalties: Creator fees on secondary sales.
// Marketplace listing struct
struct Listing {
address seller;
uint256 tokenId;
uint256 price;
bool isAuction;
uint256 endTime;
address highestBidder;
uint256 highestBid;
}
function createListing(uint256 tokenId, uint256 price) public {
require(ownerOf(tokenId) == msg.sender);
listings[tokenId] = Listing(msg.sender, tokenId, price, false, 0, address(0), 0);
}
function buy(uint256 tokenId) public payable {
Listing memory listing = listings[tokenId];
require(msg.value >= listing.price);
// Transfer ownership and funds
}💰 Royalties & Fees
🎯 Creator Compensation
Royalties allow creators to earn a percentage of secondary sales, incentivizing long-term value creation.
Royalty Implementation
EIP-2981: Standard for royalty payments. Percentage: Usually 5-10% of sale price. On-chain vs off-chain: Some marketplaces handle royalties separately.
import "@openzeppelin/contracts/interfaces/IERC2981.sol";
contract RoyaltyNFT is ERC721, IERC2981 {
address private _royaltyRecipient;
uint256 private _royaltyPercentage;
function royaltyInfo(uint256 tokenId, uint256 salePrice)
external view override returns (address receiver, uint256 royaltyAmount) {
return (_royaltyRecipient, (salePrice * _royaltyPercentage) / 100);
}
function setRoyaltyRecipient(address recipient) public onlyOwner {
_royaltyRecipient = recipient;
}
}🌐 IPFS & Decentralized Storage
🎯 Storing NFT Data
IPFS (InterPlanetary File System) provides decentralized, content-addressed storage for NFT metadata and assets.
IPFS Benefits
Content-addressed: Files identified by their hash (CID). Permanent: Files persist as long as they're pinned. Decentralized: No single point of failure.
import { create } from 'ipfs-http-client'
// Connect to IPFS
const ipfs = create({ url: 'https://ipfs.infura.io:5001' })
// Upload NFT metadata
async function uploadMetadata(metadata) {
const result = await ipfs.add(JSON.stringify(metadata))
console.log('IPFS CID:', result.path)
return `ipfs://${result.path}`
}
// Pin file for persistence
async function pinFile(file) {
const result = await ipfs.add(file)
await ipfs.pin.add(result.cid)
return result.cid.toString()
}🖨️ Minting Processes
🎯 Creating New NFTs
Minting is the process of creating a new NFT by writing to the blockchain, assigning ownership, and setting metadata.
Minting Types
Single mint: Create one token. Batch mint: Create multiple tokens at once. Presale mint: Limited access minting.
// Minting with gas optimization
function mint(uint256 amount) public payable {
require(amount > 0 && amount <= MAX_MINT);
require(totalSupply() + amount <= MAX_SUPPLY);
require(msg.value >= amount * MINT_PRICE);
for (uint256 i = 0; i < amount; i++) {
uint256 tokenId = totalSupply() + 1;
_safeMint(msg.sender, tokenId);
}
}
// Mint to multiple addresses
function mintBatch(address[] calldata to, uint256[] calldata amounts) public onlyOwner {
require(to.length == amounts.length);
for (uint256 i = 0; i < to.length; i++) {
// Mint amounts[i] tokens to to[i]
}
}💤 Lazy Minting
🎯 Mint on Demand
Lazy minting defers on-chain minting until the first sale, saving gas costs for creators and enabling free mints.
Lazy Mint Benefits
Gas savings: No upfront minting cost. Customer pays gas: Buyer pays gas when claiming. Scalable: Mint unlimited tokens without cost.
// Lazy minting with signatures
contract LazyMintNFT is ERC721 {
mapping(bytes32 => bool) public usedSignatures;
function mintWithSignature(
address to,
string memory uri,
uint256 tokenId,
bytes memory signature
) public {
bytes32 message = keccak256(abi.encodePacked(to, uri, tokenId));
require(!usedSignatures[message]);
address signer = recoverSigner(message, signature);
require(signer == owner());
usedSignatures[message] = true;
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
}
}🧩 Fractional NFTs
🎯 Fractional Ownership
Fractional NFTs split a single NFT into multiple fungible tokens, enabling shared ownership and liquidity.
Fractionalization Process
Vault: Lock NFT in a smart contract. Tokens: Issue fungible tokens representing shares. Governance: Token holders vote on decisions.
contract FractionalNFT {
address public nftContract;
uint256 public nftId;
ERC20 public shareToken;
uint256 public totalShares;
constructor(address _nft, uint256 _id, uint256 _shares) {
nftContract = _nft;
nftId = _id;
totalShares = _shares;
// Transfer NFT to this contract
// Create share token
}
function buyShares(uint256 amount) public payable {
// Buy shares with ETH
}
function vote(uint256 proposalId) public {
// Governance voting
}
}🔄 Dynamic NFTs
🎯 Changing NFT State
Dynamic NFTs can change their metadata, appearance, or properties based on external conditions, time, or user actions.
Dynamic Mechanisms
Time-based: Unlock new forms over time. User interaction: Change based on user actions. External data: Use oracles for real-world data.
contract DynamicNFT is ERC721 {
mapping(uint256 => string) public currentState;
mapping(uint256 => uint256) public lastUpdate;
function updateState(uint256 tokenId, string memory newState) public {
require(ownerOf(tokenId) == msg.sender);
require(block.timestamp - lastUpdate[tokenId] >= 1 days);
currentState[tokenId] = newState;
lastUpdate[tokenId] = block.timestamp;
// Update metadata URI
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
// Return dynamic URI based on current state
return string(abi.encodePacked("ipfs://", currentState[tokenId]));
}
}🔒 Security & Risks
🎯 Protecting NFTs
NFT security involves protecting against scams, phishing, contract vulnerabilities, and private key theft.
Security Best Practices
Smart contract audits: Professional auditing. Multi-sig wallets: Multiple approvals for transfers. Phishing awareness: Never share private keys.
contract SecureNFT is ERC721 {
mapping(address => bool) public whitelist;
mapping(uint256 => bool) public frozen;
modifier onlyWhitelisted() {
require(whitelist[msg.sender]);
_;
}
function freeze(uint256 tokenId) public onlyOwner {
frozen[tokenId] = true;
}
function transferFrom(address from, address to, uint256 tokenId) public override {
require(!frozen[tokenId], "Token is frozen");
super.transferFrom(from, to, tokenId);
}
}🔗 Interoperability
🎯 Cross-Chain NFTs
Interoperability enables NFTs to move between different blockchains, expanding utility and reach.
Bridge Solutions
Bridges: Lock and mint on another chain. Wormhole: Cross-chain messaging. LayerZero: Omnichain NFTs.
// Cross-chain NFT with LayerZero
contract OmnichainNFT is ERC721, OApp {
using BytesLib for bytes;
function crossChainTransfer(uint256 tokenId, uint32 dstChainId) public payable {
bytes memory payload = abi.encode(tokenId, msg.sender);
_lzSend(dstChainId, payload, payable(msg.sender), address(0), bytes(""));
_burn(tokenId);
}
}🚀 Future of NFTs
🎯 Emerging Trends
AI-generated NFTs, token-gated communities, decentralized identity, metaverse integration, and real-world assets.
Future Applications
Digital identity: Soulbound tokens. Real estate: Property tokenization. Education: Certificate NFTs. Gaming: Play-to-earn, metaverse.
// Soulbound tokens (non-transferable)
contract SoulboundToken is ERC721 {
function transferFrom(address from, address to, uint256 tokenId) public override {
revert("Soulbound token cannot be transferred");
}
function safeTransferFrom(address from, address to, uint256 tokenId) public override {
revert("Soulbound token cannot be transferred");
}
}