Smart Contract

Domains

A.233eb012d34b0070.Domains

Valid From

141,168,735

Deployed

2w ago
Feb 07, 2026, 04:07:44 AM UTC

Dependents

53 imports
1import NonFungibleToken from 0x1d7e57aa55817448
2import FungibleToken from 0xf233dcee88fe0abe
3import MetadataViews from 0x1d7e57aa55817448
4import ViewResolver from 0x1d7e57aa55817448
5import FNSConfig from 0x233eb012d34b0070
6
7// Domains define the domain and sub domain resource
8// Use records and expired to store domain's owner and expiredTime
9access(all) contract Domains: NonFungibleToken {
10  // Sum the domain number with domain and subdomain
11  access(all) var totalSupply: UInt64
12
13  // Paths
14  access(all) let CollectionStoragePath: StoragePath
15  access(all) let CollectionPublicPath: PublicPath
16
17  // Domain records to store the owner of Domains.Domain resource
18  // When domain resource transfer to another user, the records will be update in the deposit func
19  access(self) let records: {String: Address}
20
21  // Expired records for Domains to check the domain's validity, will change at register and renew
22  access(self) let expired: {String: UFix64}
23
24  // Store the expired and deprecated domain records 
25  access(self) let deprecated: {String: {UInt64: DomainDeprecatedInfo}}
26
27  // Store the domains id with namehash key
28  access(self) let idMap: {String: UInt64}
29
30
31  access(all) let domainExpiredTip: String
32  access(all) let domainDeprecatedTip: String
33
34
35  // Events
36  access(all) event ContractInitialized()
37  access(all) event Withdraw(id: UInt64, from: Address?)
38  access(all) event Deposit(id: UInt64, to: Address?)
39  access(all) event Created(id: UInt64, name: String)
40  access(all) event DomainRecordChanged(name: String, resolver: Address)
41  access(all) event DomainExpiredChanged(name: String, expiredAt: UFix64)
42  access(all) event SubDomainCreated(id: UInt64, hash: String)
43  access(all) event SubDomainRemoved(id: UInt64, hash: String)
44  access(all) event SubdmoainTextChanged(nameHash: String, key: String, value: String)
45  access(all) event SubdmoainTextRemoved(nameHash: String, key: String)
46  access(all) event SubdmoainAddressChanged(nameHash: String, chainType: UInt64, address: String)
47  access(all) event SubdmoainAddressRemoved(nameHash: String, chainType: UInt64)
48  access(all) event DmoainAddressRemoved(nameHash: String, chainType: UInt64)
49  access(all) event DmoainTextRemoved(nameHash: String, key: String)
50  access(all) event DmoainAddressChanged(nameHash: String, chainType: UInt64, address: String)
51  access(all) event DmoainTextChanged(nameHash: String, key: String, value: String)
52  access(all) event DomainMinted(id: UInt64, name: String, nameHash: String, parentName: String, expiredAt: UFix64, receiver: Address)
53  access(all) event DomainVaultDeposited(nameHash: String, vaultType: String, amount: UFix64, from: Address?)
54  access(all) event DomainVaultWithdrawn(nameHash: String, vaultType: String, amount: UFix64, from: Address?)
55  access(all) event DomainCollectionAdded(nameHash: String, collectionType: String)
56  access(all) event DomainCollectionWithdrawn(nameHash: String, collectionType: String, itemId: UInt64, from: Address?)
57  access(all) event DomainCollectionDeposited(nameHash: String, collectionType: String, itemId: UInt64, from: Address?)
58  access(all) event DomainReceiveOpened(name: String)
59  access(all) event DomainReceiveClosed(name: String)
60
61
62
63  access(all) struct DomainDeprecatedInfo {
64    access(all) let id: UInt64
65    access(all) let owner: Address
66    access(all) let name: String
67    access(all) let nameHash: String
68    access(all) let parentName: String
69    access(all) let deprecatedAt: UFix64
70    access(all) let trigger: Address
71
72
73     init(
74      id: UInt64,
75      owner: Address,
76      name: String,
77      nameHash: String, 
78      parentName: String,
79      deprecatedAt: UFix64,
80      trigger: Address
81    ) {
82      self.id = id
83      self.owner = owner
84      self.name = name
85      self.nameHash = nameHash
86      self.parentName = parentName
87      self.deprecatedAt = deprecatedAt
88      self.trigger = trigger
89    }   
90  }
91
92  // Subdomain detail
93  access(all) struct SubdomainDetail {
94    access(all) let id: UInt64
95    access(all) let owner: Address
96    access(all) let name: String
97    access(all) let nameHash: String
98    access(all) let addresses: {UInt64: String}
99    access(all) let texts: {String: String}
100    access(all) let parentName: String
101    access(all) let createdAt: UFix64 
102
103    
104    init(
105      id: UInt64,
106      owner: Address,
107      name: String,
108      nameHash: String, 
109      addresses:{UInt64: String},
110      texts: {String: String},
111      parentName: String,
112      createdAt: UFix64
113    ) {
114      self.id = id
115      self.owner = owner
116      self.name = name
117      self.nameHash = nameHash
118      self.addresses = addresses
119      self.texts = texts
120      self.parentName = parentName
121      self.createdAt = createdAt
122    }   
123  }
124  
125  // Domain detail
126  access(all) struct DomainDetail {
127    access(all) let id: UInt64
128    access(all) let owner: Address
129    access(all) let name: String
130    access(all) let nameHash: String
131    access(all) let expiredAt: UFix64
132    access(all) let addresses: {UInt64: String}
133    access(all) let texts: {String: String}
134    access(all) let parentName: String
135    access(all) let subdomainCount: UInt64
136    access(all) let subdomains: {String: SubdomainDetail}
137    access(all) let createdAt: UFix64 
138    access(all) let vaultBalances: {String: UFix64}
139    access(all) let collections: {String: [UInt64]}
140    access(all) let receivable: Bool
141    access(all) let deprecated: Bool
142
143
144    init(
145      id: UInt64,
146      owner: Address,
147      name: String,
148      nameHash: String, 
149      expiredAt: UFix64,
150      addresses: {UInt64: String},
151      texts: {String: String},
152      parentName: String,
153      subdomainCount: UInt64,
154      subdomains: {String: SubdomainDetail},
155      createdAt: UFix64,
156      vaultBalances: {String: UFix64},
157      collections: {String: [UInt64]},
158      receivable: Bool,
159      deprecated: Bool
160    ) {
161      self.id = id
162      self.owner = owner
163      self.name = name
164      self.nameHash = nameHash
165      self.expiredAt = expiredAt
166      self.addresses = addresses
167      self.texts = texts
168      self.parentName = parentName
169      self.subdomainCount = subdomainCount
170      self.subdomains = subdomains
171      self.createdAt = createdAt
172      self.vaultBalances = vaultBalances
173      self.collections = collections
174      self.receivable = receivable
175      self.deprecated = deprecated
176    } 
177  }
178
179  access(all) resource interface DomainPublic {
180
181    access(all) let id: UInt64
182    access(all) let name: String
183    access(all) let nameHash: String
184    access(all) let parent: String
185    access(all) var receivable: Bool
186    access(all) let createdAt: UFix64
187   
188
189    access(all) fun getText(key: String): String?
190
191    access(all) fun getAddress(chainType: UInt64): String?
192
193    access(all) fun getAllTexts():{String: String}
194
195    access(all) fun getAllAddresses():{UInt64: String}
196
197    access(all) fun getDomainName(): String
198
199    access(all) fun getDetail(): DomainDetail
200
201    access(all) fun getSubdomainsDetail(): [SubdomainDetail]
202
203    access(all) fun getSubdomainDetail(nameHash: String): SubdomainDetail
204
205    access(all) fun depositVault(from: @{FungibleToken.Vault}, senderRef: &{FungibleToken.Receiver}?)
206
207    access(all) fun addCollection(collection: @{NonFungibleToken.Collection})
208
209    access(all) fun checkCollection(key: String): Bool
210
211    access(all) fun depositNFT(key: String, token: @{NonFungibleToken.NFT}, senderRef: &{NonFungibleToken.CollectionPublic}?)
212  }
213
214  access(all) resource interface SubdomainPublic {
215    
216    access(all) let id: UInt64
217    access(all) let name: String
218    access(all) let nameHash: String
219    access(all) let parent: String
220    access(all) let createdAt: UFix64 
221
222
223    access(all) fun getText(key: String): String?
224
225    access(all) fun getAddress(chainType: UInt64): String?
226
227    access(all) fun getAllTexts():{String: String}
228
229    access(all) fun getAllAddresses():{UInt64: String}
230
231    access(all) fun getDomainName(): String
232
233    access(all) fun getDetail(): SubdomainDetail
234  }
235
236  access(all) resource interface SubdomainPrivate {
237
238    access(all) fun setText(key: String, value: String)
239
240    access(all) fun setAddress(chainType: UInt64, address: String)
241
242    access(all) fun removeText(key: String)
243
244    access(all) fun removeAddress(chainType: UInt64)
245  }
246
247  // Domain private for Domain resource owner manage domain and subdomain
248  access(all) resource interface DomainPrivate {
249
250    access(all) fun setText(key: String, value: String)
251
252    access(all) fun setAddress(chainType: UInt64, address: String)
253
254    access(all) fun setETHAddress(address: String, publicKey: [UInt8], signature: [UInt8])
255
256    access(all) fun removeText(key: String)
257
258    access(all) fun removeAddress(chainType: UInt64)
259
260    access(all) fun createSubDomain(name: String)
261
262    access(all) fun removeSubDomain(nameHash: String)
263
264    access(all) fun setSubdomainText(nameHash: String, key: String, value: String)
265
266    access(all) fun setSubdomainAddress(nameHash: String, chainType: UInt64, address: String)
267
268    access(all) fun removeSubdomainText(nameHash: String, key: String)
269
270    access(all) fun removeSubdomainAddress(nameHash: String, chainType: UInt64)
271
272    access(all) fun withdrawVault(key: String, amount: UFix64): @{FungibleToken.Vault}
273
274    access(all) fun withdrawNFT(key: String, itemId: UInt64): @{NonFungibleToken.NFT} 
275
276    access(all) fun setReceivable(_ flag: Bool)
277
278  }
279
280  // Subdomain resource belongs Domain.NFT
281  access(all) resource Subdomain: SubdomainPublic, SubdomainPrivate {
282
283    access(all) let id: UInt64
284    access(all) let name: String
285    access(all) let nameHash: String
286    access(all) let parent: String
287    access(all) let parentNameHash: String
288    access(all) let createdAt: UFix64
289    access(self) let addresses:  {UInt64: String}
290    access(self) let texts: {String: String} 
291
292
293    init(id: UInt64, name: String, nameHash: String, parent: String, parentNameHash: String) {
294      self.id = id
295      self.name = name
296      self.nameHash = nameHash
297      self.addresses = {}
298      self.texts ={}
299      self.parent = parent
300      self.parentNameHash = parentNameHash
301      self.createdAt = getCurrentBlock().timestamp
302    }
303
304    // Get subdomain full name with parent name
305    access(all) fun getDomainName(): String {
306      let domainName = ""
307      return domainName.concat(self.name).concat(".").concat(self.parent)
308    }
309
310    // Get subdomain property
311    access(all) fun getText(key: String): String? {
312      return self.texts[key]
313    }
314
315    // Get address of subdomain
316    access(all) fun getAddress(chainType: UInt64): String? {
317      return self.addresses[chainType]!
318    }
319
320    // get all texts
321    access(all) fun getAllTexts():{String: String}{
322      return self.texts
323    }
324
325    // get all texts
326    access(all) fun getAllAddresses():{UInt64: String}{
327      return self.addresses
328    }
329
330    // get subdomain detail
331    access(all) fun getDetail(): SubdomainDetail {
332      let owner = Domains.getRecords(self.parentNameHash)!
333
334      let detail = SubdomainDetail(
335        id: self.id,
336        owner: owner,
337        name: self.getDomainName(), 
338        nameHash: self.nameHash,
339        addresses: self.getAllAddresses(),
340        texts: self.getAllTexts(),
341        parentName: self.parent,
342        createdAt: self.createdAt
343      )
344      return detail
345    }
346
347
348    access(all) fun setText(key: String, value: String){
349      pre {
350        !Domains.isExpired(self.parentNameHash) : Domains.domainExpiredTip
351      }
352      self.texts[key] = value
353
354      emit SubdmoainTextChanged(nameHash: self.nameHash, key: key, value: value)
355    }
356
357    access(all) fun setAddress(chainType: UInt64, address: String){
358      pre {
359        !Domains.isExpired(self.parentNameHash) : Domains.domainExpiredTip
360      }
361      self.addresses[chainType] = address
362
363      emit SubdmoainAddressChanged(nameHash: self.nameHash, chainType: chainType, address: address)
364
365    }
366
367    access(all) fun removeText(key: String){
368      pre {
369        !Domains.isExpired(self.parentNameHash) : Domains.domainExpiredTip
370      }
371      self.texts.remove(key: key)
372
373      emit SubdmoainTextRemoved(nameHash: self.nameHash, key: key)
374
375    }
376
377    access(all) fun removeAddress(chainType: UInt64){
378      pre {
379        !Domains.isExpired(self.parentNameHash) : Domains.domainExpiredTip
380      }
381      self.addresses.remove(key: chainType)
382
383      emit SubdmoainAddressRemoved(nameHash: self.nameHash, chainType: chainType)
384
385    }
386
387  }
388
389  // Domain resource for NFT standard
390  access(all) resource NFT: DomainPublic, DomainPrivate, ViewResolver.Resolver, NonFungibleToken.NFT{
391
392    access(all) let id: UInt64
393    access(all) let name: String
394    access(all) let nameHash: String
395    
396    access(all) let createdAt: UFix64
397    // parent domain name
398    access(all) let parent: String
399    access(all) var subdomainCount: UInt64
400
401    access(all) var receivable: Bool
402
403    access(self) var subdomains: @{String: Subdomain}
404    access(self) let addresses:  {UInt64: String}
405    access(self) let texts: {String: String}
406    access(self) var vaults: @{String: {FungibleToken.Vault}}
407    access(self) var collections: @{String: {NonFungibleToken.Collection}}
408
409    init(id: UInt64, name: String, nameHash: String, parent: String) {
410      self.id = id
411      self.name = name
412      self.nameHash = nameHash
413      self.addresses = {}
414      self.texts = {}
415      self.subdomainCount = 0
416      self.subdomains <- {}
417      self.parent = parent
418      self.vaults <- {}
419      self.collections <- {}
420      self.receivable = true
421      self.createdAt = getCurrentBlock().timestamp
422    }
423
424    access(all) view fun getViews(): [Type] {
425      return [
426        Type<MetadataViews.Display>(),
427        Type<MetadataViews.Royalties>(),
428        Type<MetadataViews.Editions>(),
429        Type<MetadataViews.ExternalURL>(),
430        Type<MetadataViews.NFTCollectionData>(),
431        Type<MetadataViews.NFTCollectionDisplay>(),
432        Type<MetadataViews.Serial>(),
433        Type<MetadataViews.Traits>()
434      ]
435    }
436
437
438    access(all) fun resolveView(_ view: Type): AnyStruct? {
439      let domainName = self.getDomainName()
440      let dataUrl = "https://flowns.org/api/data/domain/".concat(domainName)
441      let thumbnailUrl = "https://flowns.org/api/fns?domain=".concat(domainName)
442      
443      switch view {
444        case Type<MetadataViews.Display>():
445          return MetadataViews.Display(
446              name: domainName,
447              description: "Flowns domain ".concat(domainName),
448              thumbnail: MetadataViews.HTTPFile(
449                  url: thumbnailUrl
450              )
451          )
452        case Type<MetadataViews.Editions>():
453            // There is no max number of NFTs that can be minted from this contract
454            // so the max edition field value is set to nil
455            let editionInfo = MetadataViews.Edition(name: "Flowns Domains NFT Edition", number: self.id, max: nil)
456            let editionList: [MetadataViews.Edition] = [editionInfo]
457            return MetadataViews.Editions(
458                editionList
459            )
460        case Type<MetadataViews.Serial>():
461            return MetadataViews.Serial(
462                self.id
463            )
464        case Type<MetadataViews.Royalties>():
465            let receieverCap =  Domains.account.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
466            let royalty= MetadataViews.Royalty(receiver: receieverCap, cut: 0.1, description: "Flowns will take 10% as second trade royalty fee")
467            return MetadataViews.Royalties([royalty])
468            
469        case Type<MetadataViews.ExternalURL>():
470            return MetadataViews.ExternalURL("https://flowns.org/domain/".concat(domainName))
471        case Type<MetadataViews.NFTCollectionData>():
472            return MetadataViews.NFTCollectionData(
473                storagePath: Domains.CollectionStoragePath,
474                publicPath: Domains.CollectionPublicPath,
475                publicCollection: Type<&Domains.Collection>(),
476                publicLinkedType: Type<&Domains.Collection>(),
477                createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
478                    return <- Domains.createEmptyCollection(nftType: Type<@Domains.NFT>())
479                })
480            )
481        case Type<MetadataViews.NFTCollectionDisplay>():
482            let squareMedia = MetadataViews.Media(
483                file: MetadataViews.HTTPFile(
484                    url: "https://www.flowns.org/assets/flowns_logo_light.svg"
485                ),
486                mediaType: "image/svg+xml"
487            )
488            let banerMedia = MetadataViews.Media(
489                file: MetadataViews.HTTPFile(
490                    url: "https://www.flowns.org/assets/flowns_logo_light.svg"
491                ),
492                mediaType: "image/svg+xml"
493            )
494            return MetadataViews.NFTCollectionDisplay(
495              name: "The Flowns domain Collection",
496              description: "This collection is managed by Flowns and present the ownership of domain.",
497              externalURL: MetadataViews.ExternalURL("https://flowns.org"),
498              squareImage: squareMedia,
499              bannerImage: banerMedia,
500              socials: {
501                  "twitter": MetadataViews.ExternalURL("https://twitter.com/flownsorg"),
502                  "discord": MetadataViews.ExternalURL("https://discord.gg/fXz4gBaYXd"),
503                  "website": MetadataViews.ExternalURL("https://flowns.org"),
504                  "medium": MetadataViews.ExternalURL("https://medium.com/@Flowns")
505              }
506            )
507        case Type<MetadataViews.Traits>():
508          let excludedTraits = ["mintedTime"]
509          let traitsView = MetadataViews.dictToTraits(dict: self.texts, excludedNames: excludedTraits)
510          // mintedTime is a unix timestamp, we should mark it with a displayType so platforms know how to show it.
511          return traitsView
512      }
513      return nil
514    }
515    
516    // get domain full name with root domain
517    access(all) fun getDomainName(): String {
518      return self.name.concat(".").concat(self.parent)
519    }
520
521    access(all) fun getText(key: String): String? {
522      
523      return self.texts[key]
524    }
525
526    access(all) fun getAddress(chainType: UInt64): String? {
527     
528      return self.addresses[chainType]!
529    }
530
531    access(all) fun getAllTexts():{String: String}{
532      return self.texts
533    }
534
535    access(all) fun getAllAddresses():{UInt64: String}{
536      return self.addresses
537    }
538
539    access(all) fun setText(key: String, value: String){
540      pre {
541        !Domains.isExpired(self.nameHash) : Domains.domainExpiredTip
542        !Domains.isDeprecated(nameHash: self.nameHash, domainId: self.id) : Domains.domainDeprecatedTip
543        key != "_ethSig": "`_ethSig` is reserved"
544      }
545      self.texts[key] = value
546
547      emit DmoainTextChanged(nameHash: self.nameHash, key: key, value: value)
548    }
549
550    access(all) fun setAddress(chainType: UInt64, address: String){
551      pre {
552        !Domains.isExpired(self.nameHash) : Domains.domainExpiredTip
553        !Domains.isDeprecated(nameHash: self.nameHash, domainId: self.id) : Domains.domainDeprecatedTip
554      }
555
556      switch chainType {
557        case 0 :
558          self.addresses[chainType] = address
559          emit DmoainAddressChanged(nameHash: self.nameHash, chainType: chainType, address: address)
560          return
561        case 1 :
562          // verify domain name texts with signature
563          return
564        default:
565          return
566      }
567
568    }
569
570    access(all) fun setETHAddress(address: String, publicKey: [UInt8], signature: [UInt8]) {
571      pre {
572        !Domains.isExpired(self.nameHash) : Domains.domainExpiredTip
573        !Domains.isDeprecated(nameHash: self.nameHash, domainId: self.id) : Domains.domainDeprecatedTip
574        address.length > 0 : "Cannot verify empty message"
575      }
576
577      let prefix = "\u{19}Ethereum Signed Message:\n".concat(address.length.toString())
578
579      assert(Domains.verifySignature(message: address, messagePrefix: prefix, hashTag: nil, hashAlgorithm: HashAlgorithm.KECCAK_256, publicKey: publicKey, signatureAlgorithm: SignatureAlgorithm.ECDSA_secp256k1, signature: signature) == true, message: "Invalid signature")
580
581      let owner = Domains.getRecords(self.nameHash)
582      assert(owner != nil, message: "Can not find owner")
583
584      if owner!.toString() == address {
585
586        let now = getCurrentBlock().timestamp
587        let ethAddr = Domains.ethPublicKeyToAddress(publicKey: publicKey)
588        let verifyStr = "{".concat("\"timestamp\": \"").concat(now.toString()).concat("\", \"message\": \"").concat(address).concat("\", \"publicKey\": \"").concat(String.encodeHex(publicKey)).concat("\", \"ethAddr\": \"").concat(ethAddr).concat("\"}")
589
590        self.texts["_ethSig"] = verifyStr
591
592        
593        self.addresses[1] = ethAddr
594
595        emit DmoainAddressChanged(nameHash: self.nameHash, chainType: 1, address: address)
596      }
597    }
598
599
600    access(all) fun removeText(key: String){
601        pre {
602        !Domains.isExpired(self.nameHash) : Domains.domainExpiredTip
603        !Domains.isDeprecated(nameHash: self.nameHash, domainId: self.id) : Domains.domainDeprecatedTip
604      }
605
606      self.texts.remove(key: key)
607      
608      emit DmoainTextRemoved(nameHash: self.nameHash, key: key)
609    }
610
611    access(all) fun removeAddress(chainType: UInt64){
612        pre {
613        !Domains.isExpired(self.nameHash) : Domains.domainExpiredTip
614        !Domains.isDeprecated(nameHash: self.nameHash, domainId: self.id) : Domains.domainDeprecatedTip
615      }
616
617      self.addresses.remove(key: chainType)
618
619      emit DmoainAddressRemoved(nameHash: self.nameHash, chainType: chainType)
620    }
621
622    access(all) fun setSubdomainText(nameHash: String, key: String, value: String){
623      pre {
624        !Domains.isExpired(self.nameHash) : Domains.domainExpiredTip
625        !Domains.isDeprecated(nameHash: self.nameHash, domainId: self.id) : Domains.domainDeprecatedTip
626        self.subdomains[nameHash] != nil : "Subdomain not exsit..."
627      }
628
629      let subdomain = (&self.subdomains[nameHash] as &Domains.Subdomain?)!
630      subdomain.setText(key: key, value: value)
631    }
632
633    access(all) fun setSubdomainAddress(nameHash: String, chainType: UInt64, address: String){
634      pre {
635        !Domains.isExpired(self.nameHash) : Domains.domainExpiredTip
636        !Domains.isDeprecated(nameHash: self.nameHash, domainId: self.id) : Domains.domainDeprecatedTip
637        self.subdomains[nameHash] != nil : "Subdomain not exsit..."
638      }
639
640      let subdomain = (&self.subdomains[nameHash] as &Domains.Subdomain?)!
641      subdomain.setAddress(chainType: chainType, address: address)
642    }
643
644    access(all) fun removeSubdomainText(nameHash: String, key: String) {
645      pre {
646        !Domains.isExpired(self.nameHash) : Domains.domainExpiredTip
647        !Domains.isDeprecated(nameHash: self.nameHash, domainId: self.id) : Domains.domainDeprecatedTip
648        self.subdomains[nameHash] != nil : "Subdomain not exsit..."
649      }
650      let subdomain = (&self.subdomains[nameHash] as &Domains.Subdomain?)!
651      subdomain.removeText(key: key)
652    }
653
654    access(all) fun removeSubdomainAddress(nameHash: String, chainType: UInt64) {
655      pre {
656        !Domains.isExpired(self.nameHash) : Domains.domainExpiredTip
657        !Domains.isDeprecated(nameHash: self.nameHash, domainId: self.id) : Domains.domainDeprecatedTip
658        self.subdomains[nameHash] != nil : "Subdomain not exsit..."
659      }
660      let subdomain = (&self.subdomains[nameHash] as &Domains.Subdomain?)!
661      subdomain.removeAddress(chainType: chainType)
662    }
663    
664    access(all) fun getDetail(): DomainDetail {
665
666      let owner = Domains.getRecords(self.nameHash) ?? panic("Cannot get owner")
667      let expired = Domains.getExpiredTime(self.nameHash) ?? panic("Cannot get expired time")
668      
669      let subdomainKeys = self.subdomains.keys
670      var subdomains: {String: SubdomainDetail} = {}
671      for subdomainKey in subdomainKeys {
672        let subRef = (&self.subdomains[subdomainKey] as &Subdomain?)!
673        let detail = subRef.getDetail()
674        subdomains[subdomainKey] = detail
675      }
676
677      var vaultBalances: {String: UFix64} = {}
678      let vaultKeys = self.vaults.keys
679      for vaultKey in vaultKeys {
680        let balRef = (&self.vaults[vaultKey] as &{FungibleToken.Vault}?)!
681        let balance = balRef.balance
682        vaultBalances[vaultKey] = balance
683      }
684
685      var collections:{String: [UInt64]} = {}
686
687      let collectionKeys = self.collections.keys
688      for collectionKey in collectionKeys {
689        let collectionRef = (&self.collections[collectionKey] as &{NonFungibleToken.Collection}?)!
690        let ids = collectionRef!.getIDs()
691        collections[collectionKey] = ids
692      }
693
694      let detail = DomainDetail(
695        id: self.id,
696        owner: owner,
697        name: self.getDomainName(), 
698        nameHash: self.nameHash,
699        expiredAt: expired,
700        addresses: self.getAllAddresses(),
701        texts: self.getAllTexts(),
702        parentName: self.parent,
703        subdomainCount: self.subdomainCount,
704        subdomains: subdomains,
705        createdAt: self.createdAt,
706        vaultBalances: vaultBalances,
707        collections: collections,
708        receivable: self.receivable,
709        deprecated: Domains.isDeprecated(nameHash: self.nameHash, domainId: self.id)
710      )
711      return detail
712    }
713
714    access(all) fun getSubdomainDetail(nameHash: String): SubdomainDetail {
715      let subdomainRef = (&self.subdomains[nameHash] as &Subdomain?)!
716      return subdomainRef.getDetail()
717    }
718
719
720    access(all) fun getSubdomainsDetail(): [SubdomainDetail] {
721      let ids = self.subdomains.keys
722      var subdomains:[SubdomainDetail] = []
723      for id in ids {
724        // todo
725        let subRef = (&self.subdomains[id] as &Subdomain?)!
726        let detail = subRef.getDetail()
727        subdomains.append(detail)
728      }
729      return subdomains
730    }
731
732    // create subdomain with domain
733    access(all) fun createSubDomain(name: String){
734      pre {
735        !Domains.isExpired(self.nameHash) : Domains.domainExpiredTip
736        !Domains.isDeprecated(nameHash: self.nameHash, domainId: self.id) : Domains.domainDeprecatedTip
737      }
738
739      let subForbidChars = self.getText(key: "_forbidChars") ?? "!@#$%^&*()<>? ./"
740
741      for char in subForbidChars.utf8 {
742      if name.utf8.contains(char) {
743        panic("Domain name illegal ...")
744      }
745    }
746    
747      let domainHash = self.nameHash.slice(from: 2, upTo: 66)
748      let nameSha = String.encodeHex(HashAlgorithm.SHA3_256.hash(name.utf8))
749      let nameHash = "0x".concat(String.encodeHex(HashAlgorithm.SHA3_256.hash(domainHash.concat(nameSha).utf8)))
750      
751      if self.subdomains[nameHash] != nil {
752        panic("Subdomain already existed.")
753      }
754      let subdomain <- create Subdomain(
755        id: self.subdomainCount,
756        name: name,
757        nameHash: nameHash,
758        parent: self.getDomainName(),
759        parentNameHash: self.nameHash
760      )
761
762      let oldSubdomain <- self.subdomains[nameHash] <- subdomain
763      self.subdomainCount = self.subdomainCount + (1 as UInt64)
764      
765      emit SubDomainCreated(id: self.subdomainCount, hash: nameHash)
766      destroy oldSubdomain
767    }
768
769    access(all) fun removeSubDomain(nameHash: String){
770      pre {
771        !Domains.isExpired(self.nameHash) : Domains.domainExpiredTip
772        !Domains.isDeprecated(nameHash: self.nameHash, domainId: self.id) : Domains.domainDeprecatedTip
773      }
774      self.subdomainCount = self.subdomainCount - (1 as UInt64)
775      let oldToken <- self.subdomains.remove(key: nameHash) ?? panic("missing subdomain")
776
777      emit SubDomainRemoved(id: oldToken.id, hash: nameHash)
778
779      destroy oldToken
780
781    }
782
783    access(all) fun depositVault(from: @{FungibleToken.Vault}, senderRef: &{FungibleToken.Receiver}?) {
784      pre {
785        !Domains.isExpired(self.nameHash) : Domains.domainExpiredTip
786        !Domains.isDeprecated(nameHash: self.nameHash, domainId: self.id) : Domains.domainDeprecatedTip
787        self.receivable : "Domain is not receivable"
788      }
789      let typeKey = from.getType().identifier
790      // add type whitelist check 
791      assert(FNSConfig.checkFTWhitelist(typeKey) == true, message: "FT type is not in inbox whitelist")
792      let amount = from.balance
793      let address = from.owner?.address
794      if self.vaults[typeKey] == nil {
795        self.vaults[typeKey] <-! from
796      } else {
797        let vault = (&self.vaults[typeKey] as &{FungibleToken.Vault}?)!
798        vault.deposit(from: <- from)
799      }
800      emit DomainVaultDeposited(nameHash: self.nameHash, vaultType: typeKey, amount: amount, from: senderRef?.owner?.address )
801
802    }
803
804    access(all) fun withdrawVault(key: String, amount: UFix64): @{FungibleToken.Vault} {
805      pre {
806        self.vaults[key] != nil : "Vault not exsit..."
807      }
808      let vaultRef = (&self.vaults[key] as auth(FungibleToken.Withdraw) &{FungibleToken.Vault}?)!
809      let balance = vaultRef.balance
810      var withdrawAmount = amount
811      if amount == 0.0 {
812        withdrawAmount = balance
813      }
814      emit DomainVaultWithdrawn(nameHash: self.nameHash, vaultType: key, amount: balance, from: Domains.getRecords(self.nameHash))
815      return <- vaultRef.withdraw(amount: withdrawAmount)
816    }
817
818    access(all) fun addCollection(collection: @{NonFungibleToken.Collection}) {
819      pre {
820        !Domains.isExpired(self.nameHash) : Domains.domainExpiredTip
821        !Domains.isDeprecated(nameHash: self.nameHash, domainId: self.id) : Domains.domainDeprecatedTip
822        self.receivable : "Domain is not receivable"
823      }
824
825      let typeKey = collection.getType().identifier
826      assert(FNSConfig.checkNFTWhitelist(typeKey) == true, message: "NFT type is not in inbox whitelist")
827
828      if collection.isInstance(Type<@Domains.Collection>()) {
829        panic("Do not nest domain resource")
830      }
831      let address = collection.owner?.address
832      if self.collections[typeKey] == nil {
833        self.collections[typeKey] <-! collection
834        emit DomainCollectionAdded(nameHash: self.nameHash, collectionType: typeKey)
835      } else {
836        if collection.getIDs().length > 0 {
837          panic("collection not empty ")
838        }
839        destroy collection
840      }
841    }
842
843    access(all) fun checkCollection(key: String): Bool {
844      return self.collections[key] != nil
845    }
846
847    access(all) fun depositNFT(key: String, token: @{NonFungibleToken.NFT}, senderRef: &{NonFungibleToken.CollectionPublic}?) {
848      pre {
849        self.collections[key] != nil : "Cannot find NFT collection..."
850        !Domains.isExpired(self.nameHash) : Domains.domainExpiredTip
851        !Domains.isDeprecated(nameHash: self.nameHash, domainId: self.id) : Domains.domainDeprecatedTip
852      }
853      assert(FNSConfig.checkNFTWhitelist(key) == true, message: "NFT type is not in inbox whitelist")
854
855      let collectionRef = (&self.collections[key] as &{NonFungibleToken.Collection}?)!
856
857      emit DomainCollectionDeposited(nameHash: self.nameHash, collectionType: key, itemId: token.id, from: senderRef?.owner?.address)
858
859      collectionRef.deposit(token: <- token)
860    }
861
862    access(all) fun withdrawNFT(key: String, itemId: UInt64): @{NonFungibleToken.NFT} {
863      pre {
864        self.collections[key] != nil : "Cannot find NFT collection..."
865      }
866      let collectionRef = (&self.collections[key] as auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection}?)!
867
868      emit DomainCollectionWithdrawn(nameHash: self.nameHash, collectionType: key, itemId: itemId, from: Domains.getRecords(self.nameHash))
869
870      return <- collectionRef.withdraw(withdrawID: itemId)
871    }
872
873    access(all) fun setReceivable(_ flag: Bool) {
874      pre {
875        !Domains.isExpired(self.nameHash) : Domains.domainExpiredTip
876        !Domains.isDeprecated(nameHash: self.nameHash, domainId: self.id) : Domains.domainDeprecatedTip
877      }
878      self.receivable = flag
879      if flag == false {
880        emit DomainReceiveClosed(name: self.getDomainName())
881      } else {
882        emit DomainReceiveOpened(name: self.getDomainName())
883      }
884    }
885
886    access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
887      return <- Domains.createEmptyCollection(nftType: Type<@Domains.NFT>())
888    }
889    
890  }
891
892  access(all) resource interface CollectionPublic {
893
894    access(all) fun deposit(token: @{NonFungibleToken.NFT})
895
896    access(all) fun getIDs(): [UInt64]
897
898    access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
899
900    access(all) fun borrowDomain(id: UInt64): &Domains.NFT
901
902    access(all) fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}?
903  }
904
905  // TODO return the content for this NFT
906  access(all) resource interface CollectionPrivate {
907
908    access(account) fun mintDomain(name: String, nameHash: String, parentName: String, expiredAt: UFix64, receiver: Capability<&{NonFungibleToken.Receiver}>)
909
910    access(all) fun borrowDomainPrivate(_ id: UInt64): &Domains.NFT
911
912  }
913
914
915  // NFT collection 
916  access(all) resource Collection: NonFungibleToken.Collection, CollectionPublic, CollectionPrivate {
917
918    access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
919
920    init () {
921      self.ownedNFTs <- {}
922    }
923
924    // withdraw removes an NFT from the collection and moves it to the caller
925    access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
926      let domain <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing domain")
927      
928      emit Withdraw(id: domain.id, from: self.owner?.address)
929
930      return <-domain
931    }
932
933    access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
934
935      let token <- token as! @{NonFungibleToken.NFT, Domains.DomainPublic}
936      let id: UInt64 = token.id
937      let nameHash = token.nameHash
938
939      if Domains.isExpired(nameHash) {
940        panic(Domains.domainExpiredTip)
941      }
942
943      if Domains.isDeprecated(nameHash: token.nameHash, domainId: token.id) {
944        panic(Domains.domainDeprecatedTip)
945      }
946      // update the owner record for new domain owner
947      
948      Domains.updateRecords(nameHash: nameHash, address: self.owner?.address)
949      
950      // add the new token to the dictionary which removes the old one  
951      // todo
952      let oldToken <- self.ownedNFTs[id] <- token as! @{NonFungibleToken.NFT}
953
954      emit Deposit(id: id,to: self.owner?.address)
955
956      destroy oldToken
957    }
958
959    // getIDs returns an array of the IDs that are in the collection
960    access(all) view fun getIDs(): [UInt64] {
961
962      return self.ownedNFTs.keys
963    }
964
965     access(all) view fun getLength(): Int {
966        return self.ownedNFTs.keys.length
967    }
968
969    access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
970      return &self.ownedNFTs[id] as &{NonFungibleToken.NFT}?
971    }
972
973
974    access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
975      let nft = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
976      let domainNFT = nft as! &Domains.NFT
977      return domainNFT as &{ViewResolver.Resolver}
978    }
979
980     access(all) view fun getContractViews(resourceType: Type?): [Type] {
981      return [
982        Type<MetadataViews.NFTCollectionData>(),
983        Type<MetadataViews.NFTCollectionDisplay>()
984      ]
985    }
986    
987    // Borrow domain for public use
988    access(all) fun borrowDomain(id: UInt64): &Domains.NFT {
989      pre {
990        self.ownedNFTs[id] != nil: "domain doesn't exist"
991      }
992      let ref = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}?
993      return ref! as! &Domains.NFT
994    }
995
996    // Borrow domain for domain owner 
997    access(all) fun borrowDomainPrivate(_ id: UInt64): &Domains.NFT {
998      pre {
999        self.ownedNFTs[id] != nil: "domain doesn't exist"
1000      }
1001      let ref = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}?
1002      return ref! as! &Domains.NFT
1003    }
1004
1005    access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
1006      return <-Domains.createEmptyCollection(nftType: Type<@Domains.NFT>())
1007    }
1008
1009    /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
1010    access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
1011      let supportedTypes: {Type: Bool} = {}
1012      supportedTypes[Type<@Domains.NFT>()] = true
1013      return supportedTypes
1014    }
1015
1016    /// Returns whether or not the gisSupportedNFTTypeiven type is accepted by the collection
1017    /// A collection that can accept any type should just return true by default
1018    access(all) view fun isSupportedNFTType(type: Type): Bool {
1019      if type == Type<@Domains.NFT>() {
1020        return true
1021      } else {
1022        return false
1023      }
1024    }
1025
1026    access(account) fun mintDomain(name: String, nameHash: String, parentName: String, expiredAt: UFix64, receiver: Capability<&{NonFungibleToken.Receiver}>){
1027      
1028      if Domains.getRecords(nameHash) != nil {
1029        let isExpired = Domains.isExpired(nameHash)
1030
1031        if isExpired == false {
1032          panic("The domain is not available")
1033        }
1034        let currentOwnerAddr = Domains.getRecords(nameHash)!
1035        let account = getAccount(currentOwnerAddr)
1036
1037        var deprecatedDomain: &{Domains.DomainPublic}? = nil
1038
1039        let currentId = Domains.getDomainId(nameHash)
1040        
1041        let deprecatedInfo = DomainDeprecatedInfo(
1042          id: currentId!,
1043          owner: currentOwnerAddr,
1044          name: name,
1045          nameHash:nameHash,
1046          parentName: parentName,
1047          deprecatedAt: getCurrentBlock().timestamp,
1048          trigger: receiver.address
1049        )
1050        
1051        var deprecatedRecords: {UInt64: DomainDeprecatedInfo} = Domains.getDeprecatedRecords(nameHash) ?? {}
1052
1053        deprecatedRecords[currentId!] = deprecatedInfo
1054
1055        Domains.updateDeprecatedRecords(nameHash: nameHash, records: deprecatedRecords)
1056       
1057      }
1058
1059      let domain <- create Domains.NFT(
1060        id: Domains.totalSupply,
1061        name: name,
1062        nameHash: nameHash,
1063        parent: parentName,
1064      )
1065      // todo
1066      let nft <- domain as! @{NonFungibleToken.NFT}
1067      
1068      Domains.updateRecords(nameHash: nameHash, address: receiver.address)
1069      Domains.updateExpired(nameHash: nameHash, time: expiredAt)
1070      Domains.updateIdMap(nameHash: nameHash, id: nft.id)
1071      Domains.totalSupply = Domains.totalSupply + 1 as UInt64
1072
1073      emit DomainMinted(id: nft.id, name: name, nameHash: nameHash, parentName: parentName, expiredAt: expiredAt, receiver: receiver.address)
1074      receiver.borrow()!.deposit(token: <- nft)
1075    }
1076
1077  }
1078
1079
1080  access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
1081
1082    let collection <- create Collection()
1083    return <- collection
1084  }
1085
1086  access(all) view fun getContractViews(resourceType: Type?): [Type] {
1087    return [
1088      Type<MetadataViews.NFTCollectionData>(),
1089      Type<MetadataViews.NFTCollectionDisplay>()
1090    ]
1091  }
1092
1093  access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
1094    switch viewType {
1095      case Type<MetadataViews.NFTCollectionData>():
1096        let collectionData = MetadataViews.NFTCollectionData(
1097          storagePath: Domains.CollectionStoragePath,
1098          publicPath: Domains.CollectionPublicPath,
1099          publicCollection: Type<&Domains.Collection>(),
1100          publicLinkedType: Type<&Domains.Collection>(),
1101          createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
1102            return <-Domains.createEmptyCollection(nftType: Type<@Domains.NFT>())
1103          })
1104        )
1105        return collectionData
1106      case Type<MetadataViews.NFTCollectionDisplay>():
1107        let squareMedia = MetadataViews.Media(
1108            file: MetadataViews.HTTPFile(
1109                url: "https://www.flowns.org/assets/flowns_logo_light.svg"
1110            ),
1111            mediaType: "image/svg+xml"
1112        )
1113        let banerMedia = MetadataViews.Media(
1114            file: MetadataViews.HTTPFile(
1115                url: "https://www.flowns.org/assets/flowns_logo_light.svg"
1116            ),
1117            mediaType: "image/svg+xml"
1118        )
1119        return MetadataViews.NFTCollectionDisplay(
1120          name: "The Flowns domain Collection",
1121          description: "This collection is managed by Flowns and present the ownership of domain.",
1122          externalURL: MetadataViews.ExternalURL("https://flowns.org"),
1123          squareImage: squareMedia,
1124          bannerImage: banerMedia,
1125          socials: {
1126              "twitter": MetadataViews.ExternalURL("https://twitter.com/flownsorg"),
1127              "discord": MetadataViews.ExternalURL("https://discord.gg/fXz4gBaYXd"),
1128              "website": MetadataViews.ExternalURL("https://flowns.org"),
1129              "medium": MetadataViews.ExternalURL("https://medium.com/@Flowns")
1130          }
1131        )
1132      }
1133      return nil
1134    }
1135
1136  // Get domain's expired time in timestamp 
1137  access(all) fun getExpiredTime(_ nameHash: String) : UFix64? {
1138    return self.expired[nameHash]
1139  }
1140
1141  // Get domain's expired status
1142  access(all) view fun isExpired(_ nameHash: String): Bool {    
1143    let currentTimestamp = getCurrentBlock().timestamp
1144    let expiredTime =  self.expired[nameHash]
1145    if expiredTime != nil {
1146      return currentTimestamp >= expiredTime!
1147    }
1148    return false
1149  }
1150
1151access(all) view fun isDeprecated(nameHash: String, domainId: UInt64): Bool {
1152    let deprecatedRecords = self.deprecated[nameHash] ?? {}
1153    return deprecatedRecords[domainId] != nil
1154  }
1155
1156  // Get domain's owner address
1157  access(all) fun getRecords(_ nameHash: String) : Address? {
1158    let address = self.records[nameHash]
1159    return address
1160  }
1161
1162  // Get domain's id by namehash
1163  access(all) fun getDomainId(_ nameHash: String) : UInt64? {
1164    let id = self.idMap[nameHash]
1165    return id
1166  }
1167
1168  access(all) fun getDeprecatedRecords(_ nameHash: String): {UInt64: DomainDeprecatedInfo}? {
1169    return self.deprecated[nameHash]
1170  }
1171
1172  access(all) fun getAllRecords(): {String: Address} {
1173    return self.records
1174  }
1175
1176  access(all) fun getAllExpiredRecords(): {String: UFix64} {
1177    return self.expired
1178  }
1179
1180  access(all) fun getAllDeprecatedRecords(): {String: {UInt64: DomainDeprecatedInfo }} {
1181    return self.deprecated
1182  }
1183
1184  access(all) fun getIdMap(): {String: UInt64 } {
1185    return self.idMap
1186  }
1187
1188  access(all) fun verifySignature(message: String, messagePrefix: String?, hashTag: String?, hashAlgorithm: HashAlgorithm, publicKey: [UInt8], signatureAlgorithm: SignatureAlgorithm, signature: [UInt8]) :Bool {
1189      
1190    let messageToVerify = (messagePrefix ?? "").concat(message)
1191    let keyToVerify = PublicKey(publicKey: publicKey, signatureAlgorithm: signatureAlgorithm)
1192    let isValid = keyToVerify.verify(
1193        signature: signature,
1194        signedData: messageToVerify.utf8,
1195        domainSeparationTag: hashTag ?? "",
1196        hashAlgorithm: hashAlgorithm
1197    )
1198    if !isValid {
1199        return false
1200    }
1201    return true
1202  }
1203
1204   access(all) fun ethPublicKeyToAddress(publicKey:[UInt8]) :String {
1205    pre{
1206      publicKey.length > 0 : "Invalid public key"
1207    }
1208    let publicKeyStr = String.encodeHex(publicKey)
1209    // let pk = publicKeyStr.slice(from: 2, upTo: publicKey.length)
1210    let pkArr = publicKeyStr.decodeHex()
1211    let hashed = HashAlgorithm.KECCAK_256.hash(pkArr)
1212    let hashedStr = String.encodeHex(hashed)
1213    let len = hashedStr.length
1214    let addr = hashedStr.slice(from: len-40, upTo: len)
1215
1216    return "0x".concat(addr)
1217
1218   }
1219
1220  access(account) fun updateDeprecatedRecords(nameHash: String, records: {UInt64: DomainDeprecatedInfo}) {
1221    self.deprecated[nameHash] = records
1222  }
1223
1224  // update records in case domain name not match hash
1225  access(account) fun updateRecords(nameHash: String, address: Address?) {
1226    self.records[nameHash] = address
1227  }
1228
1229  access(account) fun updateExpired(nameHash: String, time: UFix64) {
1230    self.expired[nameHash] = time
1231  }
1232
1233  access(account) fun updateIdMap(nameHash: String, id: UInt64) {
1234    self.idMap[nameHash] = id
1235  }
1236
1237	init() {
1238
1239    self.totalSupply = 0
1240    self.CollectionPublicPath =/public/fnsDomainCollection
1241    self.CollectionStoragePath =/storage/fnsDomainCollection
1242    self.domainExpiredTip =  "Domain expired, please renew it."
1243    self.domainDeprecatedTip =  "Domain deprecated."
1244    self.records = {}
1245    self.expired = {}
1246    self.deprecated = {}
1247    self.idMap = {}
1248    let account = self.account
1249    let collection <- create Collection()
1250
1251    account.storage.save(<- collection, to: self.CollectionStoragePath)
1252
1253    let collectionCap = account.capabilities.storage.issue<&{NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic}>(self.CollectionStoragePath)
1254    account.capabilities.publish(collectionCap, at: self.CollectionPublicPath)
1255
1256    emit ContractInitialized()
1257	}
1258}
1259
1260