Smart Contract

DoodleNames

A.e81193c424cfd3fb.DoodleNames

Deployed

2d ago
Feb 26, 2026, 01:49:14 PM UTC

Dependents

0 imports
1import NonFungibleToken from 0x1d7e57aa55817448
2import MetadataViews from 0x1d7e57aa55817448
3import FIND from 0x097bafa4e0b48eef
4import Templates from 0xe81193c424cfd3fb
5import ViewResolver from 0x1d7e57aa55817448
6
7access(all) contract DoodleNames: NonFungibleToken {
8
9	access(all) var totalSupply: UInt64
10
11	access(all) event ContractInitialized()
12	//J: What fields do we want here
13	access(all) event Minted(id:UInt64, address:Address, name: String, context: {String : String})
14
15	access(all) let CollectionStoragePath: StoragePath
16	access(all) let CollectionPublicPath: PublicPath
17
18	access(all) var royalties : [Templates.Royalty]
19	access(all) let registry : {String : NamePointer}
20
21	access(all) struct NamePointer {
22		access(all) let id: UInt64
23		access(all) let name: String
24		access(all) let address: Address?
25		access(all) let characterId: UInt64?
26
27		init(id: UInt64, name: String , address: Address?, characterId: UInt64?) {
28			self.id = id
29			self.name = name
30			self.address = address
31			self.characterId = characterId
32		}
33
34		access(all) fun equipped() : Bool {
35			return self.characterId != nil
36		}
37
38	}
39
40	access(all) resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver {
41
42		access(all) let id:UInt64
43		access(all) let name: String
44
45		access(all) var nounce:UInt64
46		access(all) let royalties: MetadataViews.Royalties
47		access(all) let tag: {String : String}
48		access(all) let scalar: {String : UFix64}
49		access(all) let extra: {String : AnyStruct}
50
51		init(
52			name: String,
53		) {
54			self.nounce=0
55			self.id=self.uuid
56			self.name=name
57			self.royalties=MetadataViews.Royalties(DoodleNames.getRoyalties())
58			self.tag={}
59			self.scalar={}
60			self.extra={}
61		}
62
63		access(all) view fun getViews(): [Type] {
64			return  [
65			Type<MetadataViews.Display>(),
66			Type<MetadataViews.Royalties>(),
67			Type<MetadataViews.ExternalURL>(),
68			Type<MetadataViews.NFTCollectionData>(),
69			Type<MetadataViews.NFTCollectionDisplay>()
70			]
71		}
72
73		access(all) fun resolveView(_ view: Type): AnyStruct? {
74			switch view {
75				case Type<MetadataViews.Display>():
76					return MetadataViews.Display(
77						name: self.name,
78						description: "Every Doodle name is unique and reserved by its owner.",
79						thumbnail: MetadataViews.IPFSFile(cid: "QmVpAiutpnzp3zR4q2cUedMxsZd8h5HDeyxs9x3HibsnJb", path:nil),
80					)
81
82				case Type<MetadataViews.ExternalURL>():
83					return MetadataViews.ExternalURL("https://doodles.app")
84
85				case Type<MetadataViews.Royalties>():
86					return self.royalties
87			}
88			return DoodleNames.resolveContractView(resourceType: Type<@DoodleNames.NFT>(), viewType: view)
89		}
90
91		access(contract) fun increaseNounce() {
92			self.nounce=self.nounce+1
93		}
94
95		access(account) fun withdrawn() {
96			DoodleNames.registry[self.name] = NamePointer(id: self.id, name: self.name , address: self.owner?.address, characterId: nil)
97		}
98
99		access(account) fun deposited(owner: Address?, characterId: UInt64?) {
100			if let o = owner {
101				DoodleNames.registry[self.name] = NamePointer(id: self.id, name: self.name , address: owner, characterId: characterId)
102			}
103			DoodleNames.registry[self.name] = NamePointer(id: self.id, name: self.name , address: self.owner?.address, characterId: characterId)
104		}
105
106		access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
107			return <- DoodleNames.createEmptyCollection(nftType: Type<@DoodleNames.NFT>())
108		}
109	}
110
111	access(all) resource Collection: NonFungibleToken.Collection {
112		// dictionary of NFT conforming tokens
113		// NFT is a resource type with an `UInt64` ID field
114		access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
115
116		init () {
117			self.ownedNFTs <- {}
118		}
119
120		// withdraw removes an NFT from the collection and moves it to the caller
121		access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
122			let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
123			let typedToken <- token as! @NFT
124			typedToken.withdrawn()
125			return <-typedToken
126		}
127
128		// deposit takes a NFT and adds it to the collections dictionary
129		// and adds the ID to the id array
130		access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
131			let token <- token as! @NFT
132
133			let id: UInt64 = token.id
134
135			token.increaseNounce()
136			token.deposited(owner: self.owner?.address, characterId: nil)
137			// add the new token to the dictionary which removes the old one
138			let oldToken <- self.ownedNFTs[id] <- token
139
140			destroy oldToken
141		}
142
143		// getIDs returns an array of the IDs that are in the collection
144		access(all) view fun getIDs(): [UInt64] {
145			return self.ownedNFTs.keys
146		}
147
148		// borrowNFT gets a reference to an NFT in the collection
149		// so that the caller can read its metadata and call its methods
150		access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
151			return &self.ownedNFTs[id]
152		}
153
154		access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
155			if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? as! &DoodleNames.NFT? {
156                return nft
157            }
158            return nil
159		}
160
161		access(all) view fun getLength(): Int {
162            return self.ownedNFTs.keys.length
163        }
164
165        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
166            let supportedTypes: {Type: Bool} = {}
167            supportedTypes[Type<@DoodleNames.NFT>()] = true
168            return supportedTypes
169        }
170
171        access(all) view fun isSupportedNFTType(type: Type): Bool {
172           if type == Type<@DoodleNames.NFT>() {
173        	return true
174           } else {
175        	return false
176           }
177		}
178
179		access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
180			return <- DoodleNames.createEmptyCollection(nftType: Type<@DoodleNames.NFT>())
181		}
182	}
183
184	// public function that anyone can call to create a new empty collection
185	access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
186		return <- create Collection()
187	}
188
189	// mintNFT mints a new NFT with a new ID
190	// and deposit it in the recipients collection using their collection reference
191	//The distinction between sending in a reference and sending in a capability is that when you send in a reference it cannot be stored. So it can only be used in this method
192	//while a capability can be stored and used later. So in this case using a reference is the right choice, but it needs to be owned so that you can have a good event
193
194	//TODO: this needs to be access account
195	access(account) fun mintNFT(
196		recipient: &{NonFungibleToken.Receiver},
197		name: String,
198		context: {String:String}
199	){
200		pre {
201			recipient.owner != nil : "Recipients NFT collection is not owned"
202			!self.registry.containsKey(name) : "Name already exist. Name : ".concat(name)
203		}
204
205		if (!FIND.validateFindName(name)){
206			panic("This name is not valid for registering")
207		}
208
209		DoodleNames.totalSupply = DoodleNames.totalSupply + 1
210
211		// create a new NFT
212		var newNFT <- create NFT(
213			name: name,
214		)
215
216		//Always emit events on state changes! always contain human readable and machine readable information
217		//J: discuss that fields we want in this event. Or do we prefer to use the richer deposit event, since this is really done in the backend
218		emit Minted(id:newNFT.id, address:recipient.owner!.address, name: name, context: context)
219		// deposit it in the recipient's account using their reference
220		recipient.deposit(token: <-newNFT)
221
222	}
223
224	access(account) fun mintName(
225		name: String,
226		context: {String:String},
227		address:Address,
228	) : @NFT{
229		pre {
230			!self.registry.containsKey(name) : "Name already exist. Name : ".concat(name)
231		}
232
233		if (!FIND.validateFindName(name)){
234			panic("This name is not valid for registering")
235		}
236
237		DoodleNames.totalSupply = DoodleNames.totalSupply + 1
238
239		// create a new NFT
240		var newNFT <- create NFT(
241			name: name,
242		)
243
244		emit Minted(id:newNFT.id, address:address, name: name, context: context)
245
246		return <- newNFT
247	}
248
249	access(all) fun isNameFree(_ name:String) : Bool{
250		return !self.registry.containsKey(name)
251	}
252
253	access(all) fun getRoyalties() : [MetadataViews.Royalty] {
254		let royalties : [MetadataViews.Royalty] = []
255		for r in DoodleNames.royalties {
256			royalties.append(r.getRoyalty())
257		}
258		return royalties
259	}
260
261	access(all) fun setRoyalties(_ r: [Templates.Royalty]) {
262		self.royalties = r
263	}
264
265	access(all) view fun getContractViews(resourceType: Type?): [Type] {
266        return [
267            Type<MetadataViews.NFTCollectionDisplay>(),
268            Type<MetadataViews.NFTCollectionData>()
269        ]
270    }
271
272    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
273        switch viewType {
274            case Type<MetadataViews.NFTCollectionDisplay>():
275				let externalURL = MetadataViews.ExternalURL("https://doodles.app")
276				let squareImage = MetadataViews.Media(file: MetadataViews.IPFSFile(cid: "QmVpAiutpnzp3zR4q2cUedMxsZd8h5HDeyxs9x3HibsnJb", path:nil), mediaType:"image/png")
277				let bannerImage = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://res.cloudinary.com/hxn7xk7oa/image/upload/v1675121458/doodles2_banner_ee7a035d05.jpg"), mediaType: "image/jpeg")
278				return MetadataViews.NFTCollectionDisplay(name: "DoodleNames", description: "Every Doodle name is unique and reserved by its owner.", externalURL: externalURL, squareImage: squareImage, bannerImage: bannerImage, socials: { "discord": MetadataViews.ExternalURL("https://discord.gg/doodles"), "twitter" : MetadataViews.ExternalURL("https://twitter.com/doodles")})
279			case Type<MetadataViews.NFTCollectionData>():
280				return MetadataViews.NFTCollectionData(
281					storagePath: DoodleNames.CollectionStoragePath,
282					publicPath: DoodleNames.CollectionPublicPath,
283					publicCollection: Type<&Collection>(),
284					publicLinkedType: Type<&Collection>(),
285					createEmptyCollectionFunction: (fun(): 
286						@{NonFungibleToken.Collection} {return <- DoodleNames.createEmptyCollection(nftType: Type<@DoodleNames.NFT>())})
287					)
288        }
289        return nil
290    }
291
292	init() {
293		// Initialize the total supply
294		self.totalSupply = 0
295		self.royalties = []
296		self.registry = {}
297
298		// Set the named paths
299		self.CollectionStoragePath = /storage/doodleNames
300		self.CollectionPublicPath = /public/doodleNames
301
302		self.account.storage.save<@{NonFungibleToken.Collection}>(<- DoodleNames.createEmptyCollection(nftType: Type<@DoodleNames.NFT>()), to: DoodleNames.CollectionStoragePath)
303		let cap = self.account.capabilities.storage.issue<&Collection>(DoodleNames.CollectionStoragePath)
304		self.account.capabilities.publish(cap, at: DoodleNames.CollectionPublicPath)
305
306		emit ContractInitialized()
307	}
308}