Skip to content

Commit

Permalink
BaseNFT interfaces and helper contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
austinkline committed May 29, 2024
1 parent 9d11f92 commit ced139d
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 98 deletions.
90 changes: 87 additions & 3 deletions contracts/nft/BaseCollection.cdc → contracts/nft/BaseNFT.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "ViewResolver"
import "MetadataViews"
import "BaseNFTVars"
import "FlowtyDrops"

import "NFTMetadata"

// A few primary challenges that have come up in thinking about how to define base-level interfaces
// for collections and NFTs:
Expand All @@ -21,7 +21,86 @@ import "FlowtyDrops"
// pieces to and modify the code to their liking. This could achieve the best of both worlds where there is minimal work to get something
// off the ground, but doesn't close the door to customization in the future. This could come at the cost of duplicated resource definitions,
// or could have the risk of circular imports depending on how we resolve certain pieces of information about a collection.
access(all) contract interface BaseCollection: ViewResolver {
access(all) contract interface BaseNFT: ViewResolver {


access(all) resource interface NFT: NonFungibleToken.NFT {
// This is the id entry that corresponds to an NFTs NFTMetadata.Container entry.
// Some NFTs might share the same data, so we want to permit reusing storage where possible
access(all) metadataID: UInt64

access(all) view fun getViews(): [Type] {
return [
Type<MetadataViews.Display>(),
Type<MetadataViews.Serial>(),
Type<MetadataViews.Traits>(),
Type<MetadataViews.Editions>(),
Type<MetadataViews.ExternalURL>(),
Type<MetadataViews.NFTCollectionData>(),
Type<MetadataViews.NFTCollectionDisplay>()
]
}

access(all) fun resolveView(_ view: Type): AnyStruct? {
if view == Type<MetadataViews.Serial>() {
return self.id
}

let rt = self.getType()
let segments = rt.identifier.split(separator: ".")
let addr = AddressUtils.parseAddress(rt)!
let tmp = getAccount(addr).contracts.borrow<&{BaseNFTVars}>(name: segments[2])
if tmp == nil {
return nil
}

let c = tmp!
let tmpMd = c.MetadataCap.borrow()
if tmpMd == nil {
return nil
}

let md = tmpMd!
switch view {
case Type<MetadataViews.NFTCollectionData>():
let pathIdentifier = StringUtils.join([segments[2], segments[1]], "_")
return MetadataViews.NFTCollectionData(
storagePath: StoragePath(identifier: pathIdentifier)!,
publicPath: PublicPath(identifier: pathIdentifier)!,
publicCollection: Type<&{NonFungibleToken.Collection}>(),
publicLinkedType: Type<&{NonFungibleToken.Collection}>(),
createEmptyCollectionFunction: fun(): @{NonFungibleToken.Collection} {
let addr = AddressUtils.parseAddress(rt)!
let c = getAccount(addr).contracts.borrow<&{BaseNFTVars}>(name: segments[2])!
return <- c.createEmptyCollection(nftType: rt)
}
)
case Type<MetadataViews.NFTCollectionDisplay>():
return md.collectionInfo.collectionDisplay
}

if let entry = md.borrowMetadata(id: self.metadataID) {
switch view {
case Type<MetadataViews.Traits>():
return entry.traits
case Type<MetadataViews.Editions>():
return entry.editions
case Type<MetadataViews.Display>():
let num = (entry.editions?.infoList?.length ?? 0) > 0 ? entry.editions!.infoList[0].number : self.id

return MetadataViews.Display(
name: entry.name.concat(" #").concat(num.toString()),
description: entry.description,
thumbnail: NFTMetadata.UriFile(entry.thumbnail.uri())
)
case Type<MetadataViews.ExternalURL>():
return entry.externalURL
}
}

return nil
}
}

// The base collection is an interface that attmepts to take more boilerplate
// off of NFT-standard compliant definitions.
Expand Down Expand Up @@ -93,7 +172,12 @@ access(all) contract interface BaseCollection: ViewResolver {
)
case Type<MetadataViews.NFTCollectionDisplay>():
let c = getAccount(addr).contracts.borrow<&{BaseNFTVars}>(name: segments[2])!
return c.collectionDisplay
let md = c.MetadataCap.borrow()
if md == nil {
return nil
}

return md!.collectionInfo.collectionDisplay
case Type<FlowtyDrops.DropResolver>():
return FlowtyDrops.DropResolver(cap: acct.capabilities.get<&{FlowtyDrops.ContainerPublic}>(FlowtyDrops.ContainerPublicPath))
}
Expand Down
12 changes: 0 additions & 12 deletions contracts/nft/BaseNFTMetadata.cdc

This file was deleted.

6 changes: 3 additions & 3 deletions contracts/nft/BaseNFTVars.cdc
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import "MetadataViews"
import "NonFungibleToken"
import "NFTMetadata"

access(all) contract interface BaseNFTVars {
access(all) let collectionDisplay: MetadataViews.NFTCollectionDisplay
access(all) var totalMinted: UInt64

access(all) var MetadataCap: Capability<&NFTMetadata.Container>
access(all) var totalSupply: UInt64
access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection}
}
122 changes: 122 additions & 0 deletions contracts/nft/NFTMetadata.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import "NonFungibleToken"
import "MetadataViews"

access(all) contract NFTMetadata {
access(all) let StoragePath: StoragePath
access(all) let PublicPath: PublicPath

access(all) entitlement Owner

access(all) event MetadataFrozen(uuid: UInt64, owner: Address?)

access(all) struct CollectionInfo {
access(all) var collectionDisplay: MetadataViews.NFTCollectionDisplay

init(collectionDisplay: MetadataViews.NFTCollectionDisplay) {
self.collectionDisplay = collectionDisplay
}
}

access(all) struct Metadata {
// these are used to create the display metadata view so that we can concatenate
// the id onto it.
access(all) let name: String
access(all) let description: String
access(all) let thumbnail: {MetadataViews.File}

access(all) let traits: MetadataViews.Traits?
access(all) let editions: MetadataViews.Editions?
access(all) let externalURL: MetadataViews.ExternalURL?

access(all) let data: {String: AnyStruct} // general-purpose data bucket
init(
name: String,
description: String,
thumbnail: {MetadataViews.File},
traits: MetadataViews.Traits?,
editions: MetadataViews.Editions?,
externalURL: MetadataViews.ExternalURL?,
data: {String: AnyStruct}
) {
self.name = name
self.description = description
self.thumbnail = thumbnail

self.traits = traits
self.editions = editions
self.externalURL = externalURL

self.data = {}
}
}

access(all) resource Container {
access(all) var collectionInfo: CollectionInfo
access(all) let metadata: {UInt64: Metadata}
access(all) var frozen: Bool

access(all) fun borrowMetadata(id: UInt64): &Metadata? {
return &self.metadata[id]
}

access(Owner) fun addMetadata(id: UInt64, data: Metadata) {
pre {
self.metadata[id] == nil: "id already has metadata assigned"
}

self.metadata[id] = data
}

access(Owner) fun freeze() {
self.frozen = true
emit MetadataFrozen(uuid: self.uuid, owner: self.owner?.address)
}

init(collectionInfo: CollectionInfo) {
self.collectionInfo = collectionInfo
self.metadata = {}
self.frozen = false
}
}

access(all) struct InitializeCaps {
access(all) let pubCap: Capability<&Container>
access(all) let ownerCap: Capability<auth(Owner) &Container>

init(pubCap: Capability<&Container>, ownerCap: Capability<auth(Owner) &Container>) {
self.pubCap = pubCap
self.ownerCap = ownerCap
}
}

access(all) fun createContainer(collectionInfo: CollectionInfo): @Container {
return <- create Container(collectionInfo: collectionInfo)
}

access(all) fun initialize(acct: auth(SaveValue, IssueStorageCapabilityController, PublishCapability) &Account, collectionInfo: CollectionInfo): InitializeCaps {
let container <- self.createContainer(collectionInfo: collectionInfo)
acct.storage.save(<-container, to: self.StoragePath)
let pubCap = acct.capabilities.storage.issue<&Container>(self.StoragePath)
let ownerCap = acct.capabilities.storage.issue<auth(Owner) &Container>(self.StoragePath)
return InitializeCaps(pubCap: pubCap, ownerCap: ownerCap)
}

access(all) struct UriFile: MetadataViews.File {
access(self) let url: String

access(all) view fun uri(): String {
return self.url
}

init(_ url: String) {
self.url = url
}
}

init() {
let identifier = "NFTMetadata_".concat(self.account.address.toString())
self.StoragePath = StoragePath(identifier: identifier)!
self.PublicPath = PublicPath(identifier: identifier)!
}
}
Loading

0 comments on commit ced139d

Please sign in to comment.