DeploySEALED

~!░?~○●&▒╱◆#╱?%▪◇#!%●%▒╱□^░^○$&■╱^$@○○╳●█■●□░&╳▒▒╳▫■╱!▪~@╳&?▫$~~

Transaction ID

Timestamp

Nov 21, 2025, 04:06:30 AM UTC
3mo ago

Block Height

133,461,652

Computation

0

Execution Fee

0.00003448 FLOW

Transaction Summary

Deploy

Contract deployment

Contract deployment

Script Arguments

0nameString
SemesterZeroV2
1codeString
import FungibleToken from 0xf233dcee88fe0abe import NonFungibleToken from 0x1d7e57aa55817448 import MetadataViews from 0x1d7e57aa55817448 import ViewResolver from 0x1d7e57aa55817448 /// SemesterZeroV2 - Clean implementation for Flunks: Semester Zero NFT Collection /// Features: /// - Pin evolution: Base → Silver → Gold → Special /// - Patch evolution: Base → Retro → Punk → Nerdy /// - GUM cost tracking for each evolution tier /// - Location-based NFTs (Paradise Motel, Crystal Springs, etc.) /// - Custom metadata at mint with evolution/reveal capabilities /// - Traits revealed and locked during first evolution /// /// V2: Fresh start without legacy Chapter5/GumDrop baggage access(all) contract SemesterZeroV2: NonFungibleToken, ViewResolver { // ======================================== // PATHS // ======================================== access(all) let CollectionStoragePath: StoragePath access(all) let CollectionPublicPath: PublicPath access(all) let AdminStoragePath: StoragePath // ======================================== // EVENTS // ======================================== access(all) event ContractInitialized() access(all) event NFTMinted(nftID: UInt64, recipient: Address, nftType: String, location: String, timestamp: UFix64) access(all) event NFTEvolved(nftID: UInt64, owner: Address, oldTier: String, newTier: String, timestamp: UFix64) access(all) event NFTBurned(nftID: UInt64, owner: Address, timestamp: UFix64) access(all) event Withdraw(id: UInt64, from: Address?) access(all) event Deposit(id: UInt64, to: Address?) // ======================================== // STATE VARIABLES // ======================================== access(all) var totalSupply: UInt64 // ======================================== // NFT RESOURCE // ======================================== access(all) resource NFT: NonFungibleToken.NFT { access(all) let id: UInt64 access(all) let nftType: String // "Pin" or "Patch" access(all) let location: String // "Paradise Motel", "Crystal Springs", etc. access(all) let recipient: Address access(all) let mintedAt: UFix64 access(all) let serialNumber: UInt64 access(all) var metadata: {String: String} // Mutable for evolution access(all) var evolutionTier: String // Pins: "Base", "Silver", "Gold", "Special" | Patches: "Base", "Retro", "Punk", "Nerdy" access(all) var traitsLocked: Bool // Traits get locked after first evolution init(id: UInt64, recipient: Address, serialNumber: UInt64, nftType: String, location: String, initialMetadata: {String: String}) { self.id = id self.nftType = nftType self.location = location self.recipient = recipient self.mintedAt = getCurrentBlock().timestamp self.serialNumber = serialNumber self.metadata = initialMetadata self.evolutionTier = "Base" self.traitsLocked = false } // Admin can evolve NFT - updates image, tier, and locks traits on first evolution access(contract) fun evolve(newMetadata: {String: String}, newTier: String) { self.metadata = newMetadata self.evolutionTier = newTier // Lock traits after first evolution (traits revealed = traits locked) if !self.traitsLocked { self.traitsLocked = true } } access(all) view fun getViews(): [Type] { return [ Type<MetadataViews.Display>(), Type<MetadataViews.NFTCollectionData>(), Type<MetadataViews.NFTCollectionDisplay>(), Type<MetadataViews.Royalties>(), Type<MetadataViews.ExternalURL>(), Type<MetadataViews.Serial>() ] } access(all) fun resolveView(_ view: Type): AnyStruct? { switch view { case Type<MetadataViews.Display>(): return MetadataViews.Display( name: self.metadata["name"]!, description: self.metadata["description"]!, thumbnail: MetadataViews.HTTPFile(url: self.metadata["image"]!) ) case Type<MetadataViews.NFTCollectionData>(): return SemesterZeroV2.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.NFTCollectionData>()) case Type<MetadataViews.NFTCollectionDisplay>(): return SemesterZeroV2.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.NFTCollectionDisplay>()) case Type<MetadataViews.Royalties>(): let royaltyCap = SemesterZeroV2.getRoyaltyReceiverCapability() if !royaltyCap.check() { return MetadataViews.Royalties([]) } return MetadataViews.Royalties([ MetadataViews.Royalty( receiver: royaltyCap, cut: 0.10, description: "Flunks: Semester Zero creator royalty" ) ]) case Type<MetadataViews.ExternalURL>(): return MetadataViews.ExternalURL("https://flunks.net") case Type<MetadataViews.Serial>(): return MetadataViews.Serial(self.serialNumber) } return nil } access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} { return <-SemesterZeroV2.createEmptyCollection(nftType: Type<@SemesterZeroV2.NFT>()) } } // ======================================== // NFT COLLECTION // ======================================== access(all) resource Collection: NonFungibleToken.Collection, ViewResolver.ResolverCollection { access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}} init() { self.ownedNFTs <- {} } access(all) view fun getLength(): Int { return self.ownedNFTs.length } access(all) view fun getIDs(): [UInt64] { return self.ownedNFTs.keys } access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? { return &self.ownedNFTs[id] } access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} { let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("NFT not found in collection") let nft <- token as! @SemesterZeroV2.NFT emit Withdraw(id: nft.id, from: self.owner?.address) return <-nft } access(all) fun deposit(token: @{NonFungibleToken.NFT}) { let nft <- token as! @SemesterZeroV2.NFT let id = nft.id let oldToken <- self.ownedNFTs[id] <- nft destroy oldToken emit Deposit(id: id, to: self.owner?.address) } access(all) view fun getSupportedNFTTypes(): {Type: Bool} { return {Type<@SemesterZeroV2.NFT>(): true} } access(all) view fun isSupportedNFTType(type: Type): Bool { return type == Type<@SemesterZeroV2.NFT>() } access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} { return <-SemesterZeroV2.createEmptyCollection(nftType: Type<@SemesterZeroV2.NFT>()) } // Borrow specific NFT with full access (needed for evolution function) access(all) view fun borrowSemesterZeroNFT(id: UInt64): &NFT? { if self.ownedNFTs[id] != nil { let ref = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? return ref as! &NFT? } return nil } // MetadataViews.ResolverCollection - Required for Token List access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? { if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? { return nft as &{ViewResolver.Resolver} } return nil } } // ======================================== // ADMIN RESOURCE // ======================================== access(all) resource Admin { // GUM costs for evolution (stored in Admin resource) access(all) var evolutionCosts: {String: UFix64} init() { // Initialize with default costs self.evolutionCosts = { "Pin_Silver": 100.0, "Pin_Gold": 250.0, "Pin_Special": 500.0, "Patch_Retro": 100.0, "Patch_Punk": 250.0, "Patch_Nerdy": 500.0 } } /// Mint NFT with custom type and metadata /// nftType: "Pin" or "Patch" /// location: "Paradise Motel", "Crystal Springs", etc. /// metadata: Must include "name", "description", "image", and any traits access(all) fun mintNFT( recipientAddress: Address, nftType: String, location: String, metadata: {String: String} ) { pre { nftType == "Pin" || nftType == "Patch": "NFT type must be Pin or Patch" metadata["name"] != nil: "Metadata must include name" metadata["description"] != nil: "Metadata must include description" metadata["image"] != nil: "Metadata must include image" } // Get recipient's collection capability let recipientCap = getAccount(recipientAddress) .capabilities.get<&SemesterZeroV2.Collection>(SemesterZeroV2.CollectionPublicPath) assert(recipientCap.check(), message: "Recipient does not have SemesterZeroV2 collection set up") let recipient = recipientCap.borrow()! // Mint NFT let nftID = SemesterZeroV2.totalSupply let serialNumber = SemesterZeroV2.totalSupply + 1 // Ensure metadata has required fields var fullMetadata = metadata fullMetadata["nftType"] = nftType fullMetadata["location"] = location fullMetadata["serialNumber"] = serialNumber.toString() fullMetadata["evolutionTier"] = "Base" fullMetadata["collection"] = "Flunks: Semester Zero" let nft <- create NFT( id: nftID, recipient: recipientAddress, serialNumber: serialNumber, nftType: nftType, location: location, initialMetadata: fullMetadata ) SemesterZeroV2.totalSupply = SemesterZeroV2.totalSupply + 1 // Deposit to recipient recipient.deposit(token: <-nft) emit NFTMinted( nftID: nftID, recipient: recipientAddress, nftType: nftType, location: location, timestamp: getCurrentBlock().timestamp ) } /// Evolve an NFT /// Pins: Base → Silver → Gold → Special /// Patches: Base → Retro → Punk → Nerdy /// newMetadata: Must include updated "image" /// Traits get locked after first evolution /// NOTE: GUM payment happens off-chain (Supabase) - this function just updates the NFT access(all) fun evolveNFT( userAddress: Address, nftID: UInt64, newTier: String, newMetadata: {String: String} ) { pre { newTier == "Silver" || newTier == "Gold" || newTier == "Special" || newTier == "Retro" || newTier == "Punk" || newTier == "Nerdy": "Invalid evolution tier" newMetadata["image"] != nil: "New metadata must include image" } // Get user's collection let collectionRef = getAccount(userAddress) .capabilities.get<&SemesterZeroV2.Collection>(SemesterZeroV2.CollectionPublicPath) .borrow() ?? panic("User does not have SemesterZeroV2 collection") // Borrow the NFT let nftRef = collectionRef.borrowSemesterZeroNFT(id: nftID) ?? panic("Could not borrow NFT reference") let oldTier = nftRef.evolutionTier // Update metadata to include evolution tier var fullMetadata = newMetadata fullMetadata["evolutionTier"] = newTier // Evolve the NFT nftRef.evolve(newMetadata: fullMetadata, newTier: newTier) emit NFTEvolved( nftID: nftID, owner: userAddress, oldTier: oldTier, newTier: newTier, timestamp: getCurrentBlock().timestamp ) } /// Update GUM costs for evolution (admin only) access(all) fun updatePinCosts(silverCost: UFix64, goldCost: UFix64, specialCost: UFix64) { self.evolutionCosts["Pin_Silver"] = silverCost self.evolutionCosts["Pin_Gold"] = goldCost self.evolutionCosts["Pin_Special"] = specialCost } access(all) fun updatePatchCosts(retroCost: UFix64, punkCost: UFix64, nerdyCost: UFix64) { self.evolutionCosts["Patch_Retro"] = retroCost self.evolutionCosts["Patch_Punk"] = punkCost self.evolutionCosts["Patch_Nerdy"] = nerdyCost } /// Get current evolution costs access(all) fun getEvolutionCosts(): {String: UFix64} { return self.evolutionCosts } /// Burn (permanently destroy) an NFT from a collection access(all) fun burnNFTFromCollection(collection: auth(NonFungibleToken.Withdraw) &Collection, nftID: UInt64) { assert(collection.ownedNFTs[nftID] != nil, message: "NFT does not exist in collection") let nft <- collection.withdraw(withdrawID: nftID) let ownerAddress = collection.owner!.address emit NFTBurned(nftID: nftID, owner: ownerAddress, timestamp: getCurrentBlock().timestamp) destroy nft } } // ======================================== // CONTRACT FUNCTIONS // ======================================== access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} { return <- create Collection() } access(all) fun getRoyaltyReceiverCapability(): Capability<&{FungibleToken.Receiver}> { return getAccount(0xbfffec679fff3a94) .capabilities .get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) } // Contract-level view resolver for marketplace compatibility access(all) view fun getContractViews(resourceType: Type?): [Type] { return [ Type<MetadataViews.NFTCollectionData>(), Type<MetadataViews.NFTCollectionDisplay>() ] } access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? { switch viewType { case Type<MetadataViews.NFTCollectionData>(): return MetadataViews.NFTCollectionData( storagePath: SemesterZeroV2.CollectionStoragePath, publicPath: SemesterZeroV2.CollectionPublicPath, publicCollection: Type<&SemesterZeroV2.Collection>(), publicLinkedType: Type<&SemesterZeroV2.Collection>(), createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} { return <-SemesterZeroV2.createEmptyCollection(nftType: Type<@SemesterZeroV2.NFT>()) }) ) case Type<MetadataViews.NFTCollectionDisplay>(): let squareMedia = MetadataViews.Media( file: MetadataViews.HTTPFile(url: "https://storage.googleapis.com/flunks_public/branding/flunks-logo-square.png"), mediaType: "image/png" ) let bannerMedia = MetadataViews.Media( file: MetadataViews.HTTPFile(url: "https://storage.googleapis.com/flunks_public/branding/flunks-banner.png"), mediaType: "image/png" ) return MetadataViews.NFTCollectionDisplay( name: "Flunks: Semester Zero", description: "Collectible Pins and Patches from your journey through Flunks: Semester Zero. Evolve your NFTs as you progress!", externalURL: MetadataViews.ExternalURL("https://flunks.net"), squareImage: squareMedia, bannerImage: bannerMedia, socials: { "twitter": MetadataViews.ExternalURL("https://twitter.com/FlunksNFT"), "discord": MetadataViews.ExternalURL("https://discord.gg/flunks") } ) } return nil } // ======================================== // INIT // ======================================== init() { // Set paths self.CollectionStoragePath = /storage/SemesterZeroV2Collection self.CollectionPublicPath = /public/SemesterZeroV2Collection self.AdminStoragePath = /storage/SemesterZeroV2Admin // Initialize state self.totalSupply = 0 // Create and store Admin resource self.account.storage.save(<-create Admin(), to: self.AdminStoragePath) emit ContractInitialized() } }

Cadence Script

1transaction(name: String, code: String ) {
2		prepare(signer: auth(AddContract) &Account) {
3			signer.contracts.add(name: name, code: code.utf8 )
4		}
5	}