Lightweight Foundry repository demonstrating two example ERC‑721 contracts and scripts/tests to deploy, interact, and test them.
- BasicNft — simple ERC-721 that stores
tokenURIon-chain and allows anyone to mint with a provided metadata URI. - MoodNft — dynamic NFT whose image (SVG) toggles between HAPPY and SAD moods. The image is stored as a data URI (fully on‑chain) and the token metadata is emitted as a
data:application/json;base64,URI.
- Basic NFT (Sepolia): https://sepolia.etherscan.io/address/0xF1E71fa70c18Bd20F2714Bed92d8A25C067CeC36
- Mood NFT (Sepolia): https://sepolia.etherscan.io/address/0x8135957B4D5D739e3E43DdEBCE1cBa54AaD1BFcb
(These are included for convenience — verify on Etherscan for transaction and ABI details.)
- Repository structure
- Requirements
- Quickstart
- Environment variables
- Makefile / Common commands
- Contracts overview
- Scripts & interactions
- Tests
- Deployed contracts (live)
- Notes & security
- License
├── src/
│ ├── BasicNft.sol # Simple ERC721 storing tokenURI on-chain
│ └── MoodNft.sol # Dynamic SVG-based Mood NFT (on-chain image + JSON)
├── script/
│ ├── DeployBasicNft.s.sol
│ ├── DeployMoodNft.s.sol
│ └── Interactions.s.sol # minting + mood flipping scripts using DevOpsTools
├── test/
│ ├── unit/ # unit tests for utilities and contracts
│ └── integrations/ # integration tests (deployment + behavior)
├── images/ # (used by DeployMoodNft) contain `happy.svg` and `sad.svg`
├── Makefile # convenient commands (build, test, deploy, mint, etc.)
├── .env.example # RPC keys and private key placeholders
└── README.md
- Foundry —
forge,anvil,castinstalled. make(for convenience using the providedMakefile).- (Optional) An RPC URL + private key if you want to deploy to a remote testnet such as Sepolia.
- Clone the repo.
- Copy
.env.exampleto.envand fill in the values forSEPOLIA_RPC_URL,PRIVATE_KEY, andETHERSCAN_API_KEYif deploying to Sepolia.
cp .env.example .env
# edit .env with your keys- Install dependencies and build:
# installs dependencies listed in Makefile (forge install)
make install
make build- Run tests locally:
make test- Start a local anvil chain (if you want a local dev environment):
make anvil
# in another terminal: make deploy (with NETWORK_ARGS configured for local anvil)Use the supplied .env.example as a template. The repo expects these variables when deploying to Sepolia via the Makefile:
SEPOLIA_RPC_URL=
PRIVATE_KEY=
ETHERSCAN_API_KEY=
The Makefile exposes convenient aliases. Most important:
make install— install repo dependencies (forge installs)make build— runsforge buildmake test— runsforge testmake anvil— launchesanvil(local chain)
Deployment & interactions (the Makefile passes --rpc-url and --private-key using .env values when you include --network sepolia in ARGS):
make deploy— deployDeployBasicNftscript.make deployMood— deployDeployMoodNftscript (readsimages/dynamicNft/*.svg).make mint— mint aBasicNftusing theInteractions.s.solscript.make mintMoodNft— mint aMoodNfttoken.make flipMoodNft— flips the mood of token id0on the most recently deployedMoodNft.
You can inspect the Makefile for full options and NETWORK_ARGS handling.
- Inherits from OpenZeppelin
ERC721. - Stores a mapping
s_tokenIdToUrithat holds the token metadata URI (e.g. IPFS URI) on-chain. mintNft(string memory _tokenURI)— mints a token tomsg.senderwith the providedtokenURIand increments as_tokenCounter.tokenURI(uint256 tokenId)is overridden to return the URI stored in the contract.
Use case: straight-forward example for minting NFTs whose metadata lives off-chain (IPFS), with the token URI stored on-chain.
- Inherits OpenZeppelin
ERC721and usesBase64encoding. - Keeps an
enum Mood { HAPPY, SAD }and a mapping of tokenId →Mood. - Constructor accepts two SVG images, converts them to
data:image/svg+xml;base64,...URIs (the deploy script handles conversion) and stores them. mintNft()— mints a token tomsg.sender.flipMood(uint256 tokenId)— toggles mood betweenHAPPYandSAD. Only the owner or approved address can flip the mood — otherwise the call reverts withMoodNft__CantFlipMoodIfNotOwner().tokenURI(uint256 tokenId)— returns adata:application/json;base64,metadata URI with the current image URI embedded (100% on-chain metadata and image representation).
This contract demonstrates a dynamic NFT pattern where the on‑chain metadata changes based on an on‑chain state (mood).
-
script/DeployBasicNft.s.sol— simple deploy script forBasicNft. -
script/DeployMoodNft.s.sol— reads SVG files from./images/dynamicNft/and passes encoded data URIs to theMoodNftconstructor.svgToImageURIhelper encodes an SVG string into adata:image/svg+xml;base64,URI.
-
script/Interactions.s.sol— helper scripts usingDevOpsTools.get_most_recent_deploymentto find the latest deployed contract address and perform actions:MintBasicNft— mints a basic NFT with a hard-coded PUG metadata IPFS URI.MintMoodNft— mints a Mood NFT (owner will be the signer used to broadcast).FlipMoodNft— flips mood for token id0(useful for testing/interacting after minting).
The repo contains both unit and integration tests using forge test.
- Unit tests cover
DeployMoodNft.svgToImageURIandMoodNftbasic behavior. - Integration tests demonstrate full deploy → mint → flip flows and assert expected tokenURI values.
Run all tests with:
make test
# or
forge test-
These contracts are educational/demo-level. If you plan to adapt for production:
- Add input validation and guardrails where appropriate.
- Consider gas costs for storing large on-chain data (SVGs / JSON).
- Be careful with
publicaccess —mintNftin both contracts is currentlypublicand allows anyone to mint. - Add pausability, admin controls, or other access patterns as needed.
PRs welcome. If you want to add features (metadata on-chain compression, offchain metadata pinning helper scripts, marketplace hooks, royalties) — open an issue or a PR with tests.
Licensed under the MIT License.
If you want, I can also generate a README-formatted quick demo showing example forge script commands to deploy and mint to Sepolia using your .env values.