
Fashion Passport
On-chain digital passports for fashion, proving authenticity, provenance and ownership.
Demo
About
Digital Fashion Passport gives physical fashion items a verifiable digital identity that can follow them throughout their lifetime. Brands can create passports containing product information, materials and provenance, while owners can verify authenticity and transfer the passport when an item is resold. The goal is to create a transparent, persistent history connecting physical fashion with on-chain ownership.
How it's made
So the whole project is essentially an ERC721 contract deployed to Sepolia at 0x67ad5c6b250f08b1cfd749cc371cdd140d81a61f, hooked up to a bare-bones frontend built with just vanilla HTML, CSS, and JavaScript. I deliberately skipped frameworks, build tools, and any kind of backend to keep things simple. The contract itself, FashionPassport.sol, runs on Solidity 0.8.26. Every passport lives as a struct containing the brand, model, registrar, createdAt timestamp, and metadataURI, all mapped by token ID with an internal counter tracking the count. Because this was my first blockchain hackathon, I actually wrote the core ownership logic by hand first. I built my own mapping, transfer function, and require checks just to wrap my head around what an NFT really does under the hood before leaning on OpenZeppelin. Swapping it over later ended up being pretty painless: the custom struct and createPassport logic stayed untouched, token ownership shifted to OpenZeppelin through _safeMint, and I swapped out my custom transferPassport function for the standard transferFrom. On the tooling side, I stuck entirely to Foundry. I leaned on forge for building and running tests, anvil to spin up a local chain and rehearse deployments, cast whenever I needed to poke the contract from the command line, and a forge script to handle the actual deployment. To make sure nothing slipped through, I wrote five unit tests covering creation, initial ownership, checking that IDs start at 1 and increment properly, transfers, and ensuring nobody can move an asset they do not own. Sensitive keys stay locked away in a gitignored .env file backed by a committed example template, and CI takes care of formatting, builds, and test runs on every single push. For the frontend, ethers.js v6 pulls straight from a CDN to talk with the deployed contract. The browser just manages a provider, a MetaMask signer, and the contract instance itself. That means no private keys ever touch the browser, leaving users to sign everything directly through their own wallets. While Alchemy handles RPC duty during deployment and terminal scripts, the web UI relies completely on the user's active wallet connection. A few small implementation details are worth pointing out. I set passport IDs to start at 1 instead of 0 because Solidity mappings initialize empty slots to zeroed values, meaning a non-zero createdAt timestamp doubles as a clean existence check. Since transactions do not just hand back return values off-chain, the frontend grabs the newly minted token ID by decoding the Transfer event log with an interface parser. The gallery UI also rebuilds directly from live chain state instead of guessing with optimistic updates, which keeps the screen from showing phantom data that does not actually exist on-chain. On top of that, switching accounts quickly can fire off overlapping requests, so I tagged each fetch cycle with a sequence number to ensure only the latest load ever renders. Finally, any address input runs through ethers.isAddress so it validates the full checksum instead of just checking character length. Figuring out what belongs on-chain versus off-chain was definitely the hardest design choice. I decided to keep the core provenance data on-chain, specifically the brand, model, original registrar, creation timestamp, and the metadata link. Rich photos, lengthy descriptions, and decorative icons stay off-chain, mostly because burning gas to store display strings the contract never even touches made zero sense. That brings us to the registrar field, which is the real heart of the project. While ownership changes every time an item is sold, that original registrar never moves, meaning anyone down the road can trace a resale straight back to who actually made it.