# ✨ magical-nft > A template for starting onchain NFT projects ## Changelog All notable changes to magical-nft will be documented here. ### \[Unreleased] #### Added * Initial template release * Three-contract architecture (Main, Renderer, Storage) * SSTORE2 integration for efficient onchain storage * TypeScript utility scripts * Foundry deployment scripts * Vocs documentation #### Features * Modular contract design * Asset conversion pipeline (AVIF support) * Base64 encoding utilities * Contract verification scripts * Multi-network deployment support *** ### Version History Future releases will be documented here with: * **Added** - New features * **Changed** - Changes in existing functionality * **Deprecated** - Soon-to-be removed features * **Removed** - Removed features * **Fixed** - Bug fixes * **Security** - Security improvements ## Features magical-nft comes packed with features to help you build onchain NFT projects efficiently. ### Core Features #### 🎨 Fully Onchain Storage All your NFT assets are stored directly on the blockchain using SSTORE2, an efficient storage pattern that reduces gas costs significantly. #### 📦 Modular Contract Architecture The template separates concerns into three main contracts: * **Main Contract** - ERC721 implementation with minting logic * **Storage Contract** - SSTORE2-based asset storage * **Renderer Contract** - Dynamic metadata and image generation #### 🔧 Developer Tools Built-in scripts for common development tasks: | Script | Description | | ------------------ | -------------------------------------------- | | `setup` | Initialize a new project from the template | | `extract-html` | Extract HTML assets from source files | | `convert-to-avif` | Convert images to AVIF format | | `base64-encode` | Encode assets to base64 | | `upload-all-files` | Upload all assets to the storage contract | | `verify` | Verify deployed contracts on block explorers | #### 🚀 Deployment Scripts Foundry deployment scripts for flexible deployment options: * **Full Deploy** - Deploy all contracts at once * **Individual Deploy** - Deploy contracts separately * **Upload Scripts** - Upload assets after deployment ### Technical Stack * **Solidity** - Smart contract development * **Foundry** - Testing and deployment framework * **Bun** - TypeScript runtime for scripts * **SSTORE2** - Efficient onchain storage * **OpenZeppelin** - Battle-tested contract libraries ## What is magical-nft? magical-nft is a comprehensive template for building fully onchain NFT projects. It provides a modular architecture that separates concerns into distinct contracts, making your projects easier to maintain and upgrade. ### Why Use magical-nft? #### Fully Onchain Unlike traditional NFTs that store metadata and images on IPFS or centralized servers, magical-nft stores everything directly on the blockchain. This means: * **Permanence** - Your NFT assets will exist as long as the blockchain exists * **Decentralization** - No dependency on external services * **Composability** - Other contracts can easily interact with your NFT data #### Modular Architecture The template uses a three-contract architecture: 1. **Main Contract** - Handles minting, ownership, and core logic 2. **Storage Contract** - Stores assets using SSTORE2 for gas efficiency 3. **Renderer Contract** - Generates tokenURI and metadata This separation allows you to upgrade individual components without affecting the entire system. #### Developer Experience * Pre-configured Foundry setup * TypeScript scripts for common tasks * Comprehensive deployment scripts * Asset conversion and upload tools ### Use Cases * Generative art NFTs * Onchain game items * Dynamic NFTs with evolving metadata * Collectibles with rich onchain data ## Deploying This guide covers deploying your magical-nft contracts to various networks. ### Deployment Options magical-nft provides several deployment scripts: | Script | Description | | ----------------------------------------- | ----------------------------- | | `DeployFull{{ProjectName}}.s.sol` | Deploy all contracts at once | | `DeployOnly{{ProjectName}}.s.sol` | Deploy only the main contract | | `DeployOnly{{ProjectName}}Renderer.s.sol` | Deploy only the renderer | | `DeployOnly{{ProjectName}}Storage.s.sol` | Deploy only storage | ### Local Development #### Start Anvil ```bash anvil ``` #### Deploy All Contracts ```bash forge script script/DeployFull{{ProjectName}}.s.sol \ --rpc-url http://localhost:8545 \ --broadcast ``` ### Testnet Deployment #### Configure Environment Make sure your `.env` file has the required values: ```bash SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY PRIVATE_KEY=0x... ETHERSCAN_API_KEY=... ``` #### Deploy to Sepolia ```bash source .env forge script script/DeployFull{{ProjectName}}.s.sol \ --rpc-url $SEPOLIA_RPC_URL \ --broadcast \ --verify ``` ### Mainnet Deployment :::warning Always test thoroughly on testnet before mainnet deployment. Double-check all contract parameters. ::: #### Pre-flight Checklist * [ ] All tests passing * [ ] Contracts verified on testnet * [ ] Metadata rendering correctly * [ ] Gas costs estimated * [ ] Owner addresses confirmed #### Deploy to Mainnet ```bash forge script script/DeployFull{{ProjectName}}.s.sol \ --rpc-url $MAINNET_RPC_URL \ --broadcast \ --verify \ --slow ``` The `--slow` flag helps avoid rate limiting during verification. ### Post-Deployment #### Verify Contracts If automatic verification failed: ```bash bun run verify ``` #### Upload Assets After deployment, upload your assets: ```bash bun run upload-all-files ``` #### Record Addresses Save your deployed contract addresses in a safe place: * Main Contract: `0x...` * Renderer: `0x...` * Storage: `0x...` ### Upgrading Contracts To upgrade the renderer without affecting existing tokens: 1. Deploy new renderer: `DeployOnly{{ProjectName}}Renderer.s.sol` 2. Call `setRenderer(newAddress)` on main contract 3. Verify new metadata is rendering correctly ## Uploading Assets This guide explains how to upload your NFT assets to the blockchain using the storage contract. ### Asset Pipeline magical-nft provides a complete pipeline for preparing and uploading assets: ``` Source Files → Convert → Encode → Upload → Verify ``` ### Preparing Assets #### 1. Convert Images to AVIF AVIF provides excellent compression for onchain storage: ```bash bun run convert-to-avif ``` This converts images in `assets/` to AVIF format. #### 2. Base64 Encode Encode assets to base64 for embedding in metadata: ```bash bun run base64-encode ``` #### 3. Extract HTML If using HTML-based NFTs, extract and minify: ```bash bun run extract-html ``` ### Configure Upload List Edit `config/filesToUpload.json` to specify files to upload: ```json { "files": [ { "key": "image", "path": "assets/image.avif" }, { "key": "animation", "path": "html/output.html" } ] } ``` ### Upload Files #### Upload All Files ```bash bun run upload-all-files ``` This uploads all files listed in the config. #### Upload Single File ```bash bun run upload-file ``` ### Verify & Extract After uploading, verify the data was stored correctly and extract the final output. #### Verify Uploads Download files from the storage contract to the `tmp/` directory and verify their integrity: ```bash source .env && forge script script/VerifyUploads.s.sol --rpc-url $RPC_URL ``` #### Extract HTML Fetch the `tokenURI` for token #1, decode the metadata and HTML, and save it to `tmp/final_check.html`: ```bash bun run extract-html ``` Open `tmp/final_check.html` in your browser to verify the NFT renders correctly. #### Extract Metadata Extract just the JSON metadata from the tokenURI: ```bash bun run extract-metadata ``` ### Gas Considerations Uploading large files can be expensive. Consider: * **Chunking** - Split large files across multiple transactions * **Compression** - Use AVIF for images, minify HTML/JS * **Timing** - Upload during low gas periods * **L2s** - Consider deploying on Layer 2 for lower costs ### Storage Limits SSTORE2 can store up to \~24KB per pointer. For larger files: 1. Split into chunks 2. Store each chunk separately 3. Concatenate in the renderer ### Troubleshooting #### Transaction Reverts * Check gas limit is sufficient * Verify file size is under 24KB * Ensure storage contract is deployed #### Wrong Data Stored * Verify file encoding (UTF-8 vs binary) * Check base64 encoding is correct * Re-upload with correct data ## Installation This guide will help you set up magical-nft for your project. ### Prerequisites Before you begin, make sure you have the following installed: * [Bun](https://bun.sh/) - JavaScript runtime * [Foundry](https://book.getfoundry.sh/) - Ethereum development toolkit * [Git](https://git-scm.com/) - Version control ### Option 1: Create a New Project (Recommended) Use the CLI to scaffold a new project in a new directory: ```bash # Link the CLI tool bun link # Create a new project magical-nft create "My Project Name" ``` This will create a folder named `my-project-name` with the template set up, git initialized, and Forge dependencies installed. Then install JS dependencies: ```bash cd my-project-name bun install ``` ### Option 2: Clone and Setup Clone the repository and set up in the current directory: ```bash git clone https://github.com/nahiiko/magical-nft cd magical-nft bun run setup "My Project Name" ``` Since Forge uses git submodules, you'll need to initialize them: ```bash git init # If not already a git repo forge install foundry-rs/forge-std OpenZeppelin/openzeppelin-contracts vectorized/solady bun install ``` ### Environment Configuration Copy the example environment file: ```bash cp .env.example .env ``` Then fill in your configuration values (RPC URLs, private keys, etc.). :::warning Never commit your `.env` file to version control. It contains sensitive data like private keys. ::: ### Verify Installation Build the contracts to verify everything is set up correctly: ```bash forge build ``` You should see a successful compilation with no errors. ### Next Steps Now that you have magical-nft installed, continue to the [Quick Start](/getting-started/quick-start) guide to learn how to use it. ## Quick Start This guide will walk you through creating and deploying your first onchain NFT. ### Project Structure After running `bun run setup`, your project will have the following structure: ``` your-project/ ├── contracts/ # Solidity contracts │ ├── YourProject.sol # Main NFT contract │ ├── YourProjectRenderer.sol # Metadata renderer │ └── YourProjectStorage.sol # Asset storage ├── html/ # HTML/CSS/JS assets ├── assets/ # Image assets ├── script/ # Foundry deployment scripts ├── scripts/ # TypeScript utility scripts └── config/ # Configuration files ``` ### Customize Your NFT #### 1. Edit the HTML Template Modify the files in the `html/` directory to create your NFT's visual representation: * `{{projectName}}.html` - Main HTML structure * `{{projectName}}.css` - Styles * `{{projectName}}.js` - Interactive behavior #### 2. Add Assets Place your images in the `assets/` directory. Convert them to AVIF for optimal size: ```bash bun run convert-to-avif ``` #### 3. Configure Uploads Edit `config/filesToUpload.json` to specify which files should be uploaded onchain. ### Deploy Your NFT #### Local Development Start a local Anvil node: ```bash anvil ``` Deploy to local network: ```bash forge script script/DeployFullYourProject.s.sol --rpc-url http://localhost:8545 --broadcast ``` #### Testnet Deployment Deploy to a testnet (e.g., Sepolia): ```bash forge script script/DeployFullYourProject.s.sol --rpc-url $SEPOLIA_RPC_URL --broadcast --verify ``` ### Upload Assets After deploying, upload your assets to the storage contract: ```bash bun run upload-all-files ``` ### Verify Your NFT After uploading, verify the data was stored correctly and test the output. #### Verify Uploads Download files from the storage contract to `tmp/` and verify their integrity: ```bash source .env && forge script script/VerifyUploads.s.sol --rpc-url $RPC_URL ``` #### Extract & Preview HTML Fetch the `tokenURI`, decode the metadata and HTML, and save it to `tmp/final_check.html`: ```bash bun run extract-html ``` Open `tmp/final_check.html` in your browser to verify the NFT renders correctly. #### Extract Metadata Extract just the JSON metadata from the tokenURI: ```bash bun run extract-metadata ``` ### Next Steps * Read the [Architecture Overview](/architecture/overview) to understand how the contracts work * Learn about [Deploying](/guides/deploying) to production networks * Explore [Uploading Assets](/guides/uploading-assets) for advanced asset management ## Foundry Keystore Coming soon... ## Reusable Modules Coming soon... ## Multi-Token Support Coming soon... ## Contracts This page provides detailed documentation for each contract in the magical-nft template. ### Main Contract **File:** `contracts/{{ProjectName}}.sol` The main contract is an ERC721 implementation that serves as the entry point for your NFT. #### Key Functions ```solidity function mint(address to) external payable ``` Mints a new token to the specified address. ```solidity function tokenURI(uint256 tokenId) public view returns (string memory) ``` Returns the metadata URI for a token, delegating to the renderer. ```solidity function setRenderer(address newRenderer) external onlyOwner ``` Updates the renderer contract address. #### Inheritance * `ERC721` - OpenZeppelin's ERC721 implementation * `Ownable` - Access control for owner-only functions ### Renderer Contract **File:** `contracts/{{ProjectName}}Renderer.sol` Handles all metadata and HTML generation. #### Key Functions ```solidity function tokenURI() external view returns (string memory) ``` Generates the complete token metadata JSON (delegates to `generateMetadata()`). ```solidity function setStorageAddress(address _storageAddress) external onlyOwner ``` Updates the storage contract address. #### Internal Functions The renderer uses these private functions internally: * `generateMetadata()` - Builds the JSON metadata with animation\_url and image * `generateHTML()` - Assembles HTML from stored files (start.html, CSS, JS, end.html) * `encodeMetadataJSON()` - Encodes metadata as base64 data URI ### Storage Contract **File:** `contracts/{{ProjectName}}Storage.sol` Manages onchain storage of assets using SSTORE2. #### Key Functions ```solidity function store(string calldata key, bytes calldata data) external onlyOwner ``` Stores data with a given key. ```solidity function read(string calldata key) external view returns (bytes memory) ``` Reads data associated with a key. ```solidity function getPointer(string calldata key) external view returns (address) ``` Returns the SSTORE2 pointer for a key. #### SSTORE2 SSTORE2 stores data as contract bytecode, which is: * **Cheaper to write** - Uses CREATE instead of SSTORE * **Cheaper to read** - Uses EXTCODECOPY instead of SLOAD * **Immutable** - Once stored, data cannot be modified ### EthDrive **File:** `contracts/EthDrive.sol` A utility contract for managing file uploads and organization. ### SSTORE2 **File:** `contracts/SSTORE2.sol` The SSTORE2 library implementation for efficient onchain storage. ## Architecture Overview magical-nft uses a modular three-contract architecture designed for flexibility, gas efficiency, and upgradeability. ### Contract Structure ``` ┌─────────────────┐ ┌───────────────────────┐ ┌──────────────────────┐ │ NFT Contract │────►│ NFT Renderer │────►│ NFT Storage │ │ (ERC721) │ │ (generates │ │ (EthDrive) │ │ │ │ HTML/JSON) │ │ │ │ • tokenURI() │ │ • metadata │ │ • Files (AVIF) │ │ │ │ │ │ • HTML/CSS/JS │ └─────────────────┘ └───────────────────────┘ └──────────────────────┘ (redeployable) (redeployable) (redeployable) ``` ### Main Contract The main contract (`{{ProjectName}}.sol`) handles: * **ERC721 Implementation** - Token ownership and transfers * **Minting Logic** - Mint functions with any custom logic * **Access Control** - Owner-only functions * **Contract References** - Links to renderer and storage ### Renderer Contract The renderer contract (`{{ProjectName}}Renderer.sol`) is responsible for: * **Metadata Generation** - Building the JSON metadata * **Image Generation** - Creating SVG or HTML-based images * **Dynamic Content** - Rendering based on token state This separation allows you to upgrade the rendering logic without touching the main contract. ### Storage Contract The storage contract (`{{ProjectName}}Storage.sol`) manages: * **SSTORE2 Storage** - Efficient onchain data storage * **File Registry** - Mapping of file names to storage addresses * **Data Retrieval** - Functions to read stored data Using SSTORE2 reduces gas costs significantly compared to regular storage. ### Data Flow 1. User calls `tokenURI(tokenId)` on the NFT contract 2. NFT contract delegates to the Renderer 3. Renderer fetches required assets from Storage (via EthDrive) 4. Renderer builds and returns the complete metadata JSON with embedded HTML 5. JSON contains base64-encoded HTML/CSS/JS animation ### Benefits * **Upgradeability** - Update renderer without migrating tokens * **Gas Efficiency** - SSTORE2 reduces storage costs * **Separation of Concerns** - Each contract has a single responsibility * **Composability** - Other contracts can interact with individual components ## API Reference Complete API reference for magical-nft contracts. ### Main Contract #### State Variables | Variable | Type | Description | | ------------- | --------- | -------------------------------- | | `renderer` | `address` | Address of the renderer contract | | `storage` | `address` | Address of the storage contract | | `totalSupply` | `uint256` | Total number of minted tokens | #### Functions ##### `mint` ```solidity function mint(address to) external payable returns (uint256) ``` Mints a new token to the specified address. **Parameters:** * `to` - Address to receive the minted token **Returns:** * `uint256` - The ID of the newly minted token ##### `tokenURI` ```solidity function tokenURI(uint256 tokenId) public view returns (string memory) ``` Returns the metadata URI for a token. **Parameters:** * `tokenId` - ID of the token **Returns:** * `string` - Base64-encoded JSON metadata ##### `setRenderer` ```solidity function setRenderer(address newRenderer) external onlyOwner ``` Updates the renderer contract address. **Parameters:** * `newRenderer` - Address of the new renderer contract ### Renderer Contract #### State Variables | Variable | Type | Description | | ---------------- | --------- | ------------------------------- | | `storageAddress` | `address` | Address of the storage contract | #### Functions ##### `tokenURI` ```solidity function tokenURI() external view returns (string memory) ``` Generates complete token metadata JSON with embedded HTML animation and image. **Returns:** * `string` - Base64-encoded JSON metadata data URI ##### `setStorageAddress` ```solidity function setStorageAddress(address _storageAddress) external onlyOwner ``` Updates the storage contract address. **Parameters:** * `_storageAddress` - Address of the new storage contract ### Storage Contract #### Functions ##### `store` ```solidity function store(string calldata key, bytes calldata data) external onlyOwner ``` Stores data with a given key using SSTORE2. **Parameters:** * `key` - Identifier for the stored data * `data` - Raw bytes to store ##### `read` ```solidity function read(string calldata key) external view returns (bytes memory) ``` Retrieves stored data by key. **Parameters:** * `key` - Identifier of the stored data **Returns:** * `bytes` - The stored data ##### `getPointer` ```solidity function getPointer(string calldata key) external view returns (address) ``` Returns the SSTORE2 pointer address for a key. **Parameters:** * `key` - Identifier of the stored data **Returns:** * `address` - SSTORE2 pointer contract address ### Events #### Main Contract ```solidity event RendererUpdated(address indexed oldRenderer, address indexed newRenderer); ``` #### Storage Contract ```solidity event DataStored(string indexed key, address pointer, uint256 size); ``` ### Errors ```solidity error TokenDoesNotExist(uint256 tokenId); error InsufficientPayment(uint256 required, uint256 sent); error MaxSupplyReached(); error DataNotFound(string key); ```