# NFT Tech Stack Complete Guide ## Storage Solutions Comparison | Solution | Cost | Permanence | Decentralized | Recommended For | |------|------|--------|---------|---------| | **IPFS + Pinata** | Low ($0/mo free tier) | Depends on pin service | Yes | Early-stage projects, fast launch | | **Arweave** | One-time fee (~$0.01/MB) | **Permanent** | Yes | High-value NFTs, long-term projects | | **Filecoin** | Pay per time period | Duration of contract | Yes | Large file storage | | **Centralized Server** | Very low | Not guaranteed | No | **Not recommended** | --- ## Metadata Standard (OpenSea Compatible) ```json { "name": "My NFT #1", "description": "This is the first NFT in the collection.", "image": "ipfs://QmXxx.../1.png", "external_url": "https://myproject.com/nft/1", "attributes": [ { "trait_type": "Background", "value": "Blue" }, { "trait_type": "Eyes", "value": "Laser", "rarity": 0.02 }, { "trait_type": "Level", "value": 5, "display_type": "number" }, { "trait_type": "Birthday", "value": 1546360800, "display_type": "date" } ], "animation_url": "ipfs://QmXxx.../1.mp4" // optional, video/audio } ``` **tokenURI formats:** ``` ipfs://CID/1.json ← recommended (fully decentralized) https://api.xxx.com/1 ← works, but depends on a server ar://TXID/1.json ← Arweave (permanent) ``` --- ## Pinata (Simplest IPFS Option) ```typescript import PinataSDK from "@pinata/sdk" const pinata = new PinataSDK(process.env.PINATA_API_KEY, process.env.PINATA_SECRET) // Upload image const imgResult = await pinata.pinFileToIPFS(fs.createReadStream("./nft.png"), { pinataMetadata: { name: "NFT #1" } }) const imageURI = `ipfs://${imgResult.IpfsHash}` // Upload metadata JSON const metadata = { name: "My NFT #1", image: imageURI, attributes: [...] } const metaResult = await pinata.pinJSONToIPFS(metadata) const tokenURI = `ipfs://${metaResult.IpfsHash}` ``` --- ## Arweave (Permanent Storage) ```typescript import Arweave from "arweave" const arweave = Arweave.init({ host: "arweave.net", port: 443, protocol: "https" }) const key = JSON.parse(fs.readFileSync("arweave-key.json")) // Upload file const tx = await arweave.createTransaction({ data: fs.readFileSync("./nft.png") }, key) tx.addTag("Content-Type", "image/png") await arweave.transactions.sign(tx, key) await arweave.transactions.post(tx) const imageURI = `ar://${tx.id}` // Or use Bundlr (cheaper for bulk uploads) import { NodeBundlr } from "@bundlr-network/client" const bundlr = new NodeBundlr("https://node1.bundlr.network", "arweave", key) const response = await bundlr.uploadFile("./nft.png") ``` --- ## ERC-721 Minting Contract ```solidity import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; contract MyNFT is ERC721URIStorage, Ownable { using Counters for Counters.Counter; Counters.Counter private _tokenIds; uint256 public constant MAX_SUPPLY = 10000; uint256 public constant PRICE = 0.05 ether; bool public saleActive = false; string private _baseTokenURI; // ipfs://CID/ constructor() ERC721("My NFT", "MNFT") Ownable(msg.sender) {} // Public mint function mint(uint256 quantity) external payable { require(saleActive, "Sale not active"); require(quantity <= 10, "Max 10 per tx"); require(_tokenIds.current() + quantity <= MAX_SUPPLY, "Exceeds supply"); require(msg.value >= PRICE * quantity, "Insufficient payment"); for (uint i = 0; i < quantity; i++) { _tokenIds.increment(); _safeMint(msg.sender, _tokenIds.current()); } } // Reveal (mint first, reveal metadata later) function setBaseURI(string memory baseURI) external onlyOwner { _baseTokenURI = baseURI; } function _baseURI() internal view override returns (string memory) { return _baseTokenURI; } // EIP-2981 royalties (5%) function royaltyInfo(uint256, uint256 salePrice) external view returns (address receiver, uint256 royaltyAmount) { return (owner(), salePrice * 500 / 10000); } function withdraw() external onlyOwner { payable(owner()).transfer(address(this).balance); } } ``` --- ## ERC-1155 (Multi-Token NFT / SFT) ```solidity import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; contract GameItems is ERC1155 { uint256 public constant SWORD = 1; uint256 public constant SHIELD = 2; uint256 public constant GOLD = 3; // FT (fungible token) constructor() ERC1155("https://api.game.com/items/{id}.json") { _mint(msg.sender, SWORD, 100, ""); // 100 swords _mint(msg.sender, GOLD, 1e18, ""); // large supply of gold coins } } ``` --- ## Lazy Minting (Gas Savings) How it works: NFTs are not pre-minted; they are minted at the moment of purchase, with a signature used as the voucher. ```solidity // Seller signs a voucher offline bytes32 hash = keccak256(abi.encode(tokenId, price, uri)); bytes32 ethHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); (v, r, s) = vm.sign(sellerKey, ethHash); // Buyer verifies on-chain and mints function redeem(uint tokenId, uint price, string memory uri, bytes memory sig) external payable { require(msg.value >= price); address signer = ECDSA.recover(hash, sig); require(signer == seller, "Invalid signature"); _safeMint(msg.sender, tokenId); _setTokenURI(tokenId, uri); } ``` ## Recommended Tools - **Manifold Studio** — no-code NFT deployment - **ThirdWeb** — NFT SDK + dashboard - **Zora Protocol** — free mint + royalty infrastructure # NFT Tech Stack Complete Guide ## Storage Solutions Comparison | Solution | Cost | Permanence | Decentralized | Recommended For | |------|------|--------|---------|---------| | **IPFS + Pinata** | Low ($0/mo free tier) | Depends on pin service | Yes | Early-stage projects, fast launch | | **Arweave** | One-time fee (~$0.01/MB) | **Permanent** | Yes | High-value NFTs, long-term projects | | **Filecoin** | Pay per time period | Duration of contract | Yes | Large file storage | | **Centralized Server** | Very low | Not guaranteed | No | **Not recommended** | --- ## Metadata Standard (OpenSea Compatible) ```json { "name": "My NFT #1", "description": "This is the first NFT in the collection.", "image": "ipfs://QmXxx.../1.png", "external_url": "https://myproject.com/nft/1", "attributes": [ { "trait_type": "Background", "value": "Blue" }, { "trait_type": "Eyes", "value": "Laser", "rarity": 0.02 }, { "trait_type": "Level", "value": 5, "display_type": "number" }, { "trait_type": "Birthday", "value": 1546360800, "display_type": "date" } ], "animation_url": "ipfs://QmXxx.../1.mp4" // optional, video/audio } ``` **tokenURI formats:** ``` ipfs://CID/1.json ← recommended (fully decentralized) https://api.xxx.com/1 ← works, but depends on a server ar://TXID/1.json ← Arweave (permanent) ``` --- ## Pinata (Simplest IPFS Option) ```typescript import PinataSDK from "@pinata/sdk" const pinata = new PinataSDK(process.env.PINATA_API_KEY, process.env.PINATA_SECRET) // Upload image const imgResult = await pinata.pinFileToIPFS(fs.createReadStream("./nft.png"), { pinataMetadata: { name: "NFT #1" } }) const imageURI = `ipfs://${imgResult.IpfsHash}` // Upload metadata JSON const metadata = { name: "My NFT #1", image: imageURI, attributes: [...] } const metaResult = await pinata.pinJSONToIPFS(metadata) const tokenURI = `ipfs://${metaResult.IpfsHash}` ``` --- ## Arweave (Permanent Storage) ```typescript import Arweave from "arweave" const arweave = Arweave.init({ host: "arweave.net", port: 443, protocol: "https" }) const key = JSON.parse(fs.readFileSync("arweave-key.json")) // Upload file const tx = await arweave.createTransaction({ data: fs.readFileSync("./nft.png") }, key) tx.addTag("Content-Type", "image/png") await arweave.transactions.sign(tx, key) await arweave.transactions.post(tx) const imageURI = `ar://${tx.id}` // Or use Bundlr (cheaper for bulk uploads) import { NodeBundlr } from "@bundlr-network/client" const bundlr = new NodeBundlr("https://node1.bundlr.network", "arweave", key) const response = await bundlr.uploadFile("./nft.png") ``` --- ## ERC-721 Minting Contract ```solidity import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; contract MyNFT is ERC721URIStorage, Ownable { using Counters for Counters.Counter; Counters.Counter private _tokenIds; uint256 public constant MAX_SUPPLY = 10000; uint256 public constant PRICE = 0.05 ether; bool public saleActive = false; string private _baseTokenURI; // ipfs://CID/ constructor() ERC721("My NFT", "MNFT") Ownable(msg.sender) {} // Public mint function mint(uint256 quantity) external payable { require(saleActive, "Sale not active"); require(quantity <= 10, "Max 10 per tx"); require(_tokenIds.current() + quantity <= MAX_SUPPLY, "Exceeds supply"); require(msg.value >= PRICE * quantity, "Insufficient payment"); for (uint i = 0; i < quantity; i++) { _tokenIds.increment(); _safeMint(msg.sender, _tokenIds.current()); } } // Reveal (mint first, reveal metadata later) function setBaseURI(string memory baseURI) external onlyOwner { _baseTokenURI = baseURI; } function _baseURI() internal view override returns (string memory) { return _baseTokenURI; } // EIP-2981 royalties (5%) function royaltyInfo(uint256, uint256 salePrice) external view returns (address receiver, uint256 royaltyAmount) { return (owner(), salePrice * 500 / 10000); } function withdraw() external onlyOwner { payable(owner()).transfer(address(this).balance); } } ``` --- ## ERC-1155 (Multi-Token NFT / SFT) ```solidity import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; contract GameItems is ERC1155 { uint256 public constant SWORD = 1; uint256 public constant SHIELD = 2; uint256 public constant GOLD = 3; // FT (fungible token) constructor() ERC1155("https://api.game.com/items/{id}.json") { _mint(msg.sender, SWORD, 100, ""); // 100 swords _mint(msg.sender, GOLD, 1e18, ""); // large supply of gold coins } } ``` --- ## Lazy Minting (Gas Savings) How it works: NFTs are not pre-minted; they are minted at the moment of purchase, with a signature used as the voucher. ```solidity // Seller signs a voucher offline bytes32 hash = keccak256(abi.encode(tokenId, price, uri)); bytes32 ethHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); (v, r, s) = vm.sign(sellerKey, ethHash); // Buyer verifies on-chain and mints function redeem(uint tokenId, uint price, string memory uri, bytes memory sig) external payable { require(msg.value >= price); address signer = ECDSA.recover(hash, sig); require(signer == seller, "Invalid signature"); _safeMint(msg.sender, tokenId); _setTokenURI(tokenId, uri); } ``` ## Recommended Tools - **Manifold Studio** — no-code NFT deployment - **ThirdWeb** — NFT SDK + dashboard - **Zora Protocol** — free mint + royalty infrastructure - **OpenSea SDK** — listing/trading integration