Smart Contract
DoodleNames
A.e81193c424cfd3fb.DoodleNames
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}