Smart Contract

Flowns

A.233eb012d34b0070.Flowns

Deployed

16h ago
Feb 28, 2026, 02:29:24 AM UTC

Dependents

0 imports
1
2import FungibleToken from 0xf233dcee88fe0abe
3import NonFungibleToken from 0x1d7e57aa55817448
4import Domains from 0x233eb012d34b0070
5import FNSConfig from 0x233eb012d34b0070
6
7// Flowns is the core contract of FNS, Flowns define Root domain and admin resource
8access(all) contract Flowns {
9  // paths
10  access(all) let FlownsAdminPrivatePath: PrivatePath
11  access(all) let FlownsAdminStoragePath: StoragePath
12  access(all) let CollectionStoragePath: StoragePath
13  access(all) let CollectionPublicPath: PublicPath
14  // access(all) let CollectionPrivatePath: PrivatePath
15
16  // variables
17  access(all) var totalRootDomains: UInt64
18  // status that set register pause or not
19  access(self) var isPause: Bool
20  // for domain name on-chain validator 
21  access(self) var forbidChars: String
22  // events
23
24
25  access(all) event RootDomainDestroyed(id: UInt64)
26
27  access(all) event RootDomainCreated(name: String, nameHash: String, id: UInt64)
28
29  access(all) event RenewDomain(name: String, nameHash: String, duration: UFix64, price: UFix64)
30  
31  access(all) event RootDomainPriceChanged(name: String, key: Int, price: UFix64)
32
33  access(all) event RootDomainVaultWithdrawn(name: String, amount: UFix64)
34
35  access(all) event RootDomainServerAdded()
36
37  access(all) event FlownsAdminCreated()
38
39  access(all) event RootDomainVaultChanged()
40
41  access(all) event FlownsPaused()
42
43  access(all) event FlownsActivated()
44
45  access(all) event FlownsForbidCharsUpdated(before: String, after: String)
46
47  access(all) event RootDomainMaxLengthUpdated(domainId: UInt64, before: Int, after: Int)
48
49  access(all) event RootDomainCommissionRateUpdated(domainId: UInt64, before: UFix64, after: UFix64)
50
51  access(all) event RootDomainMintDurationUpdated(domainId: UInt64, before: UFix64, after: UFix64)
52
53  access(all) event DomainRegisterCommissionAllocated(domainId: UInt64, nameHash: String, amount: UFix64, commissionAmount: UFix64, refer: Address, receiveId: UInt64)
54
55
56  // structs 
57  access(all) struct RootDomainInfo {
58    access(all) let id: UInt64
59    access(all) let name: String
60    access(all) let nameHash: String
61    access(all) let domainCount: UInt64
62    access(all) let minRentDuration: UFix64
63    access(all) let maxDomainLength: Int
64    access(all) let prices: {Int: UFix64}
65    access(all) let commissionRate: UFix64
66
67
68    init(
69      id: UInt64,
70      name: String,
71      nameHash: String,
72      domainCount: UInt64,
73      minRentDuration: UFix64,
74      maxDomainLength: Int,
75      prices: {Int: UFix64},
76      commissionRate: UFix64
77    ) {
78      self.id = id
79      self.name = name
80      self.nameHash = nameHash
81      self.domainCount = domainCount
82      self.minRentDuration = minRentDuration
83      self.maxDomainLength = maxDomainLength
84      self.prices = prices
85      self.commissionRate = commissionRate
86    }
87  }
88
89  // resources
90  // Rootdomain is the root of domain name
91  // ex. domain 'fns.flow' 'flow' is the root domain name, and save as a resource by RootDomain
92  access(all) resource RootDomain {
93    access(all) let id: UInt64
94
95    access(all) let name: String
96    
97    // namehash is calc by eth-ens-namehash
98    access(all) let nameHash: String
99
100    access(all) var domainCount: UInt64
101
102    // Here is the vault to receive domain rent fee, every root domain has his own vault
103    // you can call Flowns.getRootVaultBalance to get balance
104    access(self) var domainVault: @{FungibleToken.Vault}
105
106    // Here is the prices store for domain rent fee
107    // When user register or renew a domain ,the rent price is get from here, and price store by {domains length: flow per second}
108    // If cannot get price, then register will not open
109    access(self) var prices: {Int: UFix64}
110
111    access(self) var minRentDuration: UFix64
112
113    access(self) var maxDomainLength: Int
114
115    access(self) var commissionRate: UFix64
116
117    // Server store the collection private resource to manage the domains
118    // Server need to init before open register
119    access(self) var server: Capability<&Domains.Collection>?
120
121    init(id: UInt64, name: String, nameHash: String, vault: @{FungibleToken.Vault}){
122      self.id = id
123      self.name = name
124      self.nameHash = nameHash
125      self.domainCount = 0
126      self.domainVault <- vault
127      self.prices = {}
128      self.server = nil
129      self.minRentDuration = 31536000.00
130      self.maxDomainLength = 30
131      self.commissionRate = 0.0
132    }
133
134    // Set CollectionPrivate to RootDomain resource
135    access(all) fun addCapability(_ cap: Capability<&Domains.Collection>) {
136      pre {
137        cap.check() : "Invalid server capablity"
138        self.server == nil : "Server already set"
139      }
140      self.server = cap
141      
142      emit RootDomainServerAdded()
143
144    }
145
146    // Query root domain info
147    access(all) fun getRootDomainInfo() : RootDomainInfo {
148      return RootDomainInfo(
149        id: self.id,
150        name: self.name,
151        nameHash: self.nameHash,
152        domainCount: self.domainCount,
153        minRentDuration: self.minRentDuration,
154        maxDomainLength: self.maxDomainLength,
155        prices: self.prices,
156        commissionRate: self.commissionRate
157
158      )
159    }
160
161    // Query root domain vault balance
162    access(all) fun getVaultBalance() : UFix64 {
163      pre {
164        self.domainVault != nil : "Vault not init yet..."
165      }
166      return self.domainVault.balance
167    }
168
169    // Deposit fee to domain Vault
170    access(all) fun depositVault(fee: @{FungibleToken.Vault}) {
171      pre {
172        self.domainVault != nil : "Vault not init yet..."
173      }
174      self.domainVault.deposit(from: <- fee)
175    }
176
177    access(all) fun getPrices(): {Int: UFix64} {
178      return self.prices
179    }
180
181    // Mint domain
182    access(account) fun mintDomain(name: String, duration: UFix64, receiver: Capability<&{NonFungibleToken.Receiver}>){
183      pre {
184        self.server != nil : "Domains collection has not been linked to the server"
185      }
186      let nameHash = Flowns.getDomainNameHash(name: name, parentNameHash: self.nameHash)
187    
188      let expiredTime = getCurrentBlock().timestamp + duration
189
190      self.server!.borrow()!.mintDomain(name: name, nameHash: nameHash, parentName: self.name, expiredAt: expiredTime, receiver: receiver)
191      self.domainCount = self.domainCount + (1 as UInt64)
192
193    }
194    // Set domain rent fee
195    access(all) fun setPrices(key: Int, price: UFix64) {
196      self.prices[key]= price
197      
198      emit RootDomainPriceChanged(name: self.name, key: key, price: price)
199    }
200
201    access(account) fun setMinRentDuration(_ duration: UFix64) {
202      let oldDuration = self.minRentDuration
203      self.minRentDuration = duration
204
205      emit RootDomainMintDurationUpdated(domainId: self.id, before: oldDuration, after: duration)
206    }
207
208    access(account) fun setMaxDomainLength(_ length: Int) {
209      let oldLength = self.maxDomainLength
210      self.maxDomainLength = length
211
212      emit RootDomainMaxLengthUpdated(domainId: self.id, before: oldLength, after: length)
213    }
214
215    access(account) fun setCommissionRate(_ rate: UFix64) {
216      let oldRate = self.commissionRate
217      self.commissionRate = rate
218
219      emit RootDomainCommissionRateUpdated(domainId: self.id, before: oldRate, after: rate)
220    }
221
222    access(contract) fun _distributeCommission(feeTokens: @{FungibleToken.Vault}, refer: Address, from: Address?): @{FungibleToken.Vault} {
223      let rentFee = feeTokens.balance
224      let commissionFee  = rentFee * self.commissionRate
225        
226      let referAcc = getAccount(refer!)
227
228      let collection = referAcc.capabilities.borrow<&Domains.Collection>(Domains.CollectionPublicPath) 
229      // let collection = collectionCap.borrow()
230      if collection != nil {
231        let ids = collection!.getIDs()
232        var defaultDomain: &Domains.NFT? = nil
233        defaultDomain = collection!.borrowDomain(id: ids[0])!
234        if ids.length > 0 {
235          for id in ids {
236            let domain = collection!.borrowDomain(id: id)!
237            let isDefault = domain.getText(key: "isDefault")
238            if isDefault == "true" {
239              defaultDomain = domain
240            }
241          }
242          if defaultDomain!.receivable == true && !Domains.isExpired(defaultDomain!.nameHash) {
243            defaultDomain!.depositVault(from: <- feeTokens.withdraw(amount: commissionFee), senderRef: nil)
244            emit DomainRegisterCommissionAllocated(domainId: self.id, nameHash: defaultDomain!.nameHash, amount: rentFee, commissionAmount: commissionFee, refer: refer!, receiveId: defaultDomain!.id)
245          }
246        }
247      }
248      return <- feeTokens
249    }
250
251
252    // Renew domain
253    access(all) fun renewDomain(domain: &Domains.NFT, duration: UFix64, feeTokens: @{FungibleToken.Vault}, refer: Address?) {
254      pre {
255        !Domains.isDeprecated(nameHash: domain.nameHash, domainId: domain.id) : "Domain already deprecated ..."
256      }
257      // When domain name longer than 10, the price will set by 10 price
258      var len = domain.name.length
259      if len > 10 {
260        len = 10
261      }
262      let price = self.getPrices()[len]
263
264      if domain.parent != self.name {
265        panic("domain not root domain's sub domain")
266      }
267      if duration < self.minRentDuration {
268        panic("Duration must greater than min rent duration ".concat(self.minRentDuration.toString()))
269      }
270     
271      if price == 0.0 || price == nil {
272        panic("Can not renew domain, rent price not set yet")
273      }
274      
275      // Calc rent price
276      let rentPrice = price! * duration 
277      
278      let rentFee = feeTokens.balance
279      
280      // check the rent fee
281      if rentFee < rentPrice {
282        panic("Not enough fee to renew your domain.")
283      }
284      var feeVault: @{FungibleToken.Vault}? <- nil
285      // distribution of commission
286      if self.commissionRate > 0.0 && refer != nil {
287        feeVault <-! self._distributeCommission(feeTokens: <- feeTokens, refer: refer!, from: Domains.getRecords(domain.nameHash))
288      } else {
289        feeVault <-! feeTokens
290      }
291
292      // Receive rent fee
293      self.domainVault.deposit(from: <- feeVault!)
294
295      let expiredAt = Domains.getExpiredTime(domain.nameHash)! + UFix64(duration)
296      // Update domain's expire time with Domains expired mapping
297      Domains.updateExpired(nameHash: domain.nameHash, time: expiredAt )
298
299      emit RenewDomain(name: domain.name, nameHash: domain.nameHash, duration: duration, price: rentFee )
300
301    }
302
303    // Register domain
304    access(all) fun registerDomain(name: String, duration: UFix64, feeTokens: @{FungibleToken.Vault}, receiver: Capability<&{NonFungibleToken.Receiver}>, refer: Address? ){
305      pre {
306        self.server != nil : "Your client has not been linked to the server"
307        name.length <= self.maxDomainLength : "Domain name can not exceed max length: ".concat(self.maxDomainLength.toString())
308      }
309
310      let nameHash = Flowns.getDomainNameHash(name: name, parentNameHash: self.nameHash)
311    
312      if Flowns.available(nameHash: nameHash) == false {
313        panic("Domain not available")
314      }
315
316      // same as renew domain
317      var len = name.length
318      if len > 10 {
319        len = 10
320      }
321
322      let price = self.getPrices()[len]
323      // limit the register and renew time longer than one year
324
325      if duration < self.minRentDuration {
326        panic("Duration must geater than min rent duration, expect: ".concat(self.minRentDuration.toString()))
327      }
328      if price == 0.0 || price == nil {
329        panic("Can not register domain, rent price not set yet")
330      }
331
332      let rentPrice = price! * duration 
333      
334      let rentFee = feeTokens.balance
335
336      if rentFee < rentPrice {
337         panic("Not enough fee to rent your domain, expect: ".concat(rentPrice.toString()))
338      }
339
340      let expiredTime = getCurrentBlock().timestamp + UFix64(duration)
341
342      var feeVault: @{FungibleToken.Vault}? <- nil
343      // distribution of commission
344      if self.commissionRate > 0.0 && refer != nil {
345        feeVault <-! self._distributeCommission(feeTokens: <- feeTokens, refer: refer!, from: Domains.getRecords(nameHash))
346      } else {
347        feeVault <-! feeTokens
348      }
349
350      self.domainVault.deposit(from: <- feeVault!)
351
352      self.server!.borrow()!.mintDomain(name: name, nameHash: nameHash, parentName: self.name, expiredAt: expiredTime, receiver: receiver)
353
354      self.domainCount = self.domainCount + (1 as UInt64)
355
356    }
357
358    // Withdraw vault fee 
359    access(account) fun withdrawVault(receiver: Capability<&{FungibleToken.Receiver}>, amount: UFix64) {
360      let vault = receiver.borrow()!
361      vault.deposit(from: <- self.domainVault.withdraw(amount: amount))
362      
363      emit RootDomainVaultWithdrawn(name: self.name, amount: amount)
364    }
365
366    access(account) fun changeRootDomainVault(vault: @{FungibleToken.Vault}) {
367
368      let balance = self.getVaultBalance()
369
370      if balance > 0.0 {
371        panic("Please withdraw the balance of the previous vault first ")
372      }
373
374      let preVault <- self.domainVault <- vault
375      
376      destroy preVault
377      // clean the price
378      self.prices = {}
379      emit RootDomainVaultChanged()
380    }
381
382  }
383
384
385  // Root domain public interface for fns user
386  access(all) resource interface RootDomainCollectionPublic {
387
388    access(all) fun getDomainInfo(domainId: UInt64) : RootDomainInfo
389
390    access(all) fun getAllDomains(): {UInt64: RootDomainInfo}
391
392    access(all) fun renewDomain(domainId: UInt64, domain: &Domains.NFT, duration: UFix64, feeTokens: @{FungibleToken.Vault}, refer: Address?)
393
394    access(all) fun renewDomainWithNameHash(nameHash: String, duration: UFix64, feeTokens: @{FungibleToken.Vault}, refer: Address?)
395
396    access(all) fun registerDomain(domainId: UInt64, name: String, duration: UFix64, feeTokens: @{FungibleToken.Vault}, receiver: Capability<&{NonFungibleToken.Receiver}>,  refer: Address? )
397
398    access(all) fun getPrices(domainId: UInt64): {Int: UFix64}
399
400    access(all) fun getVaultBalance(domainId: UInt64): UFix64
401
402  }
403
404  // Manager resource
405  access(all) resource interface RootDomainCollectionAdmin {
406
407    access(account) fun createRootDomain(
408      name: String, 
409      vault: @{FungibleToken.Vault}
410    )
411
412    access(account) fun withdrawVault(domainId: UInt64, receiver: Capability<&{FungibleToken.Receiver}>, amount: UFix64)
413
414    access(account) fun changeRootDomainVault(domainId: UInt64, vault: @{FungibleToken.Vault})
415
416    access(account) fun setPrices(domainId: UInt64, len: Int, price: UFix64)
417
418    access(account) fun setMinRentDuration(domainId: UInt64, duration: UFix64)
419
420    access(account) fun setMaxDomainLength(domainId: UInt64, length: Int)
421
422    access(account) fun setCommissionRate(domainId: UInt64, rate: UFix64)
423    
424    access(account) fun mintDomain(domainId: UInt64, name: String, duration: UFix64, receiver: Capability<&{NonFungibleToken.Receiver}>)
425
426    access(account) fun renewDomainWithAdmin(nameHash: String, duration: UFix64)
427
428  }
429
430  // Root domain Collection 
431  access(all) resource RootDomainCollection: RootDomainCollectionPublic, RootDomainCollectionAdmin {
432    // Root domains
433    access(account) var domains: @{UInt64: RootDomain}
434
435    init(
436    ) {
437      self.domains <- {}
438    }
439
440    // Create root domain
441    access(account) fun createRootDomain(
442      name: String, 
443      vault: @{FungibleToken.Vault}
444    ) {
445      
446      let nameHash = Flowns.hash(node:"", lable: name)
447      let prefix = "0x"
448
449      let rootDomain  <- create RootDomain(
450        id: Flowns.totalRootDomains,
451        name: name,
452        nameHash: prefix.concat(nameHash),
453        vault: <- vault
454      )
455
456      Flowns.totalRootDomains = Flowns.totalRootDomains + 1 as UInt64
457      emit RootDomainCreated(name: name, nameHash: nameHash,  id: rootDomain.id)
458
459      let oldDomain <- self.domains[rootDomain.id] <- rootDomain
460      destroy oldDomain
461    }
462
463    access(all) fun renewDomain(domainId: UInt64, domain: &Domains.NFT, duration: UFix64, feeTokens: @{FungibleToken.Vault}, refer: Address?) {
464      pre {
465          self.domains[domainId] != nil : "Root domain not exist..."
466        }
467      let root = self.getRootDomain(domainId)
468      root.renewDomain(domain: domain, duration: duration, feeTokens: <- feeTokens, refer: refer)
469    }
470
471    access(all) fun renewDomainWithNameHash(nameHash: String, duration: UFix64, feeTokens: @{FungibleToken.Vault}, refer: Address?) {
472      let domain = Flowns.getDomain(nameHash: nameHash) ?? panic("Can not find domain by nameHash")
473      // get all domains with access(all)
474      let rootDomains = Flowns.getAllRootDomains()!
475      let ids = rootDomains.keys
476      var rootDomain: RootDomainInfo? = nil
477      for id in ids {
478        let root = rootDomains[id]!
479        if root.name == domain.parent {
480          rootDomain = root
481        }
482      }
483      assert(rootDomain != nil, message: "Can not get root domain info")
484
485      var len = domain.name.length
486      if len > 10 {
487        len = 10
488      }
489      let price = rootDomain!.prices[len]
490      
491      if duration < rootDomain!.minRentDuration {
492        panic("Duration must greater than min rent duration ".concat(rootDomain!.minRentDuration.toString()))
493      }
494      if price == 0.0 || price == nil {
495        panic("Can not renew domain, rent price not set yet")
496      }
497      // Calc rent price
498      let rentPrice = price! * duration 
499      
500      let rentFee = feeTokens.balance
501      
502      // check the rent fee
503      if rentFee < rentPrice {
504        panic("Not enough fee to renew your domain.")
505      }
506
507      let rootDomainRef = self.getRootDomain(rootDomain!.id)!
508
509      var feeVault: @{FungibleToken.Vault}? <- nil
510      // distribution of commission
511     if rootDomain!.commissionRate > 0.0 && refer != nil {
512        feeVault <-! rootDomainRef._distributeCommission(feeTokens: <- feeTokens, refer: refer!, from: Domains.getRecords(nameHash) )
513      } else {
514        feeVault <-! feeTokens
515      }
516
517
518      rootDomainRef.depositVault(fee: <- feeVault!)
519
520      let expiredAt = Domains.getExpiredTime(nameHash)! + UFix64(duration)
521      Domains.updateExpired(nameHash: nameHash, time: expiredAt )
522      emit RenewDomain(name: domain.name, nameHash: domain.nameHash, duration: duration, price: rentFee )
523    }
524
525
526    access(all) fun registerDomain(domainId: UInt64, name: String, duration: UFix64, feeTokens: @{FungibleToken.Vault}, receiver: Capability<&{NonFungibleToken.Receiver}>, refer: Address?) {
527      pre {
528        self.domains[domainId] != nil : "Root domain not exist..."
529      }
530      let root = self.getRootDomain(domainId)
531      root.registerDomain(name: name, duration: duration, feeTokens: <-feeTokens, receiver: receiver, refer: refer )
532    }
533
534    access(all) fun getVaultBalance(domainId: UInt64): UFix64 {
535        pre {
536        self.domains[domainId] != nil : "Root domain not exist..."
537      }
538      let rootRef = &self.domains[domainId] as &RootDomain?
539
540      return rootRef!.getVaultBalance()
541    }
542
543    access(account) fun withdrawVault(domainId: UInt64, receiver: Capability<&{FungibleToken.Receiver}>, amount: UFix64) {
544      pre {
545        self.domains[domainId] != nil : "Root domain not exist..."
546      }
547      self.getRootDomain(domainId).withdrawVault(receiver: receiver, amount: amount)
548    }
549
550    access(account) fun changeRootDomainVault(domainId: UInt64, vault: @{FungibleToken.Vault}) {
551      pre {
552        self.domains[domainId] != nil : "Root domain not exist..."
553      }
554      self.getRootDomain(domainId).changeRootDomainVault(vault: <- vault)
555    }
556
557    access(account) fun mintDomain(domainId: UInt64, name: String, duration: UFix64, receiver: Capability<&{NonFungibleToken.Receiver}>) {
558        pre {
559        self.domains[domainId] != nil : "Root domain not exist..."
560      }
561      let root = self.getRootDomain(domainId)
562      root.mintDomain(name: name, duration: duration, receiver: receiver)
563    }
564
565    // Get all root domains
566    access(all) fun getAllDomains(): {UInt64: RootDomainInfo} {
567      var domainInfos: {UInt64: RootDomainInfo }= {}
568      for id in self.domains.keys {
569        let itemRef = &self.domains[id] as &RootDomain?
570        domainInfos[id] = itemRef!.getRootDomainInfo()
571      }
572      return domainInfos
573    }
574    
575    access(account) fun setPrices(domainId: UInt64, len: Int, price: UFix64){
576      self.getRootDomain(domainId).setPrices(key: len, price: price)
577    }
578
579    access(account) fun setMinRentDuration(domainId: UInt64, duration: UFix64){
580      pre {
581        duration >= 604800.00 : "Duration must be greater than one week"
582      }
583      self.getRootDomain(domainId).setMinRentDuration(duration)
584    }
585
586    access(account) fun setMaxDomainLength(domainId: UInt64, length: Int){
587      pre {
588        length > 0 && length < 50 : "Domain length must greater than 0 and smaller than 50"
589      }
590      self.getRootDomain(domainId).setMaxDomainLength(length)
591    }
592
593    access(account) fun setCommissionRate(domainId: UInt64, rate: UFix64){
594      pre {
595        rate >= 0.0 && rate <= 1.0 : "Commission rate not valid"
596      }
597      self.getRootDomain(domainId).setCommissionRate(rate)
598    }
599
600    // get domain reference
601    access(contract) fun getRootDomain(_ domainId: UInt64) : &RootDomain {
602      pre {
603        self.domains[domainId] != nil: "domain doesn't exist"
604      }
605      return (&self.domains[domainId] as &RootDomain?)!
606    }
607    // get Root domain info
608    access(all) fun getDomainInfo(domainId: UInt64): RootDomainInfo {
609      return self.getRootDomain(domainId).getRootDomainInfo()
610    }
611
612    // Query root domain's rent price
613    access(all) fun getPrices(domainId: UInt64): {Int: UFix64} {
614      return self.getRootDomain(domainId).getPrices()
615    }
616
617    // renew domain by nameHash with admin auth
618    access(account) fun renewDomainWithAdmin(nameHash: String, duration: UFix64) {
619      // pre{
620      //   Domains.getExpiredTime(nameHash) != nil : "Domain doesn't exist"
621      // }
622      assert(Domains.getExpiredTime(nameHash) != nil, message: "Can not find owner")
623      let expiredAt = Domains.getExpiredTime(nameHash)! + UFix64(duration)
624      // Update domain's expire time with Domains expired mapping
625      Domains.updateExpired(nameHash: nameHash, time: expiredAt )
626      let domain = Flowns.getDomain(nameHash: nameHash)!
627      emit RenewDomain(name: domain.name.concat(".").concat(domain.parent), nameHash: nameHash, duration: duration, price: 0.0 )
628    }
629
630  }
631
632  // Admin interface resource
633  access(all) resource interface AdminPrivate {
634
635    access(all) fun addCapability(_ cap: Capability<&Flowns.RootDomainCollection>)
636
637    access(all) fun addRootDomainCapability(domainId: UInt64, cap: Capability<&Domains.Collection>)
638
639    access(all) fun createRootDomain(name: String, vault: @{FungibleToken.Vault})
640
641    access(all) fun setRentPrice(domainId: UInt64, len: Int, price: UFix64)
642
643    access(all) fun withdrawVault(domainId: UInt64, receiver: Capability<&{FungibleToken.Receiver}>, amount: UFix64)
644
645    access(all) fun changeRootDomainVault(domainId: UInt64, vault: @{FungibleToken.Vault})
646    
647    access(all) fun mintDomain(domainId: UInt64, name: String, duration: UFix64, receiver: Capability<&{NonFungibleToken.Receiver}>)
648
649    access(all) fun setMinRentDuration(domainId: UInt64, duration: UFix64)
650
651    access(all) fun setMaxDomainLength(domainId: UInt64, length: Int)
652
653    access(all) fun setCommissionRate(domainId: UInt64, rate: UFix64)
654
655    access(all) fun setDomainForbidChars(_ chars: String)
656
657    access(all) fun setPause(_ flag: Bool)
658
659    access(all) fun updateFTWhitelist(key: String, flag: Bool)
660
661    access(all) fun updateNFTWhitelist(key: String, flag: Bool)
662
663    access(all) fun setFTWhitelist(_ val: {String: Bool})
664
665    access(all) fun setNFTWhitelist(_ val: {String: Bool})
666
667  }
668
669
670  access(all) resource Admin: AdminPrivate {
671
672    access(self) var server: Capability<&Flowns.RootDomainCollection>?
673
674    init() {
675      // Server is the root collection for manager to create and store root domain
676      self.server = nil
677    }
678
679    // init RootDomainCollection for admin
680    access(all) fun addCapability(_ cap: Capability<&Flowns.RootDomainCollection>) {
681      pre {
682        cap.check() : "Invalid server capablity"
683        self.server == nil : "Server already set"
684      }
685        self.server = cap
686    }
687
688    // init Root domain's Domains collection to create collection for domain register 
689    access(all) fun addRootDomainCapability(domainId: UInt64, cap: Capability<&Domains.Collection>) {
690      pre {
691          cap.check() : "Invalid server capablity"
692      }
693      self.server!.borrow()!.getRootDomain(domainId).addCapability(cap)
694    }
695
696    // Create root domain with admin
697    access(all) fun createRootDomain(name: String, vault: @{FungibleToken.Vault}) {
698      pre {
699        self.server != nil : "Your client has not been linked to the server"
700      }
701
702      self.server!.borrow()!.createRootDomain(name: name, vault: <- vault)
703    }
704
705    // Set rent price
706    access(all) fun setRentPrice(domainId: UInt64, len: Int, price: UFix64) {
707      pre {
708        self.server != nil : "Your client has not been linked to the server"
709      }
710
711      self.server!.borrow()!.setPrices(domainId: domainId, len: len, price: price)
712    }
713
714    access(all) fun setMinRentDuration(domainId: UInt64, duration: UFix64) {
715      pre {
716        self.server != nil : "Your client has not been linked to the server"
717      }
718
719      self.server!.borrow()!.setMinRentDuration(domainId: domainId, duration: duration)
720    }
721
722    access(all) fun setMaxDomainLength(domainId: UInt64, length: Int) {
723      pre {
724        self.server != nil : "Your client has not been linked to the server"
725      }
726
727      self.server!.borrow()!.setMaxDomainLength(domainId: domainId, length: length)
728    }
729
730    
731    access(all) fun setCommissionRate(domainId: UInt64, rate: UFix64) {
732      pre {
733        self.server != nil : "Your client has not been linked to the server"
734      }
735
736      self.server!.borrow()!.setCommissionRate(domainId: domainId, rate: rate)
737    }
738    
739    
740
741    // Withdraw vault 
742    access(all) fun withdrawVault(domainId: UInt64, receiver: Capability<&{FungibleToken.Receiver}>, amount: UFix64) {
743      pre {
744        self.server != nil : "Your client has not been linked to the server"
745      }
746
747      self.server!.borrow()!.withdrawVault(domainId: domainId, receiver: receiver, amount: amount)
748    }
749
750    // Withdraw vault 
751    access(all) fun changeRootDomainVault(domainId: UInt64, vault: @{FungibleToken.Vault}) {
752      pre {
753        self.server != nil : "Your client has not been linked to the server"
754      }
755
756      self.server!.borrow()!.changeRootDomainVault(domainId: domainId, vault: <- vault)
757    }
758
759    // Mint domain with root domain
760    access(all) fun mintDomain(domainId: UInt64, name: String, duration: UFix64,receiver: Capability<&{NonFungibleToken.Receiver}>) {
761      pre {
762        self.server != nil : "Your client has not been linked to the server"
763      }
764
765      self.server!.borrow()!.mintDomain(domainId: domainId, name: name, duration: duration, receiver: receiver)
766    }
767
768      // Renew domain with admin auth
769    access(all) fun renewDomain(nameHash: String, duration: UFix64) {
770      pre {
771        self.server != nil : "Your client has not been linked to the server"
772      }
773      self.server!.borrow()!.renewDomainWithAdmin(nameHash: nameHash, duration: duration)
774    }
775
776    access(all) fun setPause(_ flag: Bool) {
777      pre {
778        Flowns.isPause != flag : "Already done!"
779      }
780      Flowns.isPause = flag
781      if flag == true {
782        emit FlownsPaused()
783      } else {
784        emit FlownsActivated()
785      }
786    }
787
788    access(all) fun setDomainForbidChars(_ chars: String) {
789      let oldChars = Flowns.forbidChars
790      Flowns.forbidChars = chars
791      
792      emit FlownsForbidCharsUpdated(before: oldChars, after: chars)
793    }
794
795    access(all) fun updateFTWhitelist(key: String, flag: Bool) {
796      FNSConfig.updateFTWhitelist(key: key, flag: flag)
797    }
798
799    access(all) fun updateNFTWhitelist(key: String, flag: Bool) {
800      FNSConfig.updateNFTWhitelist(key: key, flag: flag)
801    }
802
803    access(all) fun setFTWhitelist(_ val: {String: Bool}) {
804      FNSConfig.setFTWhitelist(val)
805    }
806
807    access(all) fun setNFTWhitelist(_ val: {String: Bool}) {
808      FNSConfig.setNFTWhitelist(val)
809    }
810
811
812
813  }
814
815  // Create admin resource
816  access(self) fun createAdminClient(): @Admin {
817    emit FlownsAdminCreated()
818    return <- create Admin()
819  }
820
821  access(all) fun getDomainNameHash(name: String, parentNameHash: String): String {
822    
823    let prefix = "0x"
824    let forbidenChars: [UInt8] = Flowns.forbidChars.utf8
825    let nameASCII = name.utf8
826
827    for char in forbidenChars {
828      if nameASCII.contains(char) {
829        panic("Domain name illegal ...")
830      }
831    }
832
833    let domainNameHash = Flowns.hash(node: parentNameHash.slice(from: 2, upTo: 66), lable:name )
834    return prefix.concat(domainNameHash)
835  }
836
837  // calc hash with node and lable
838  access(all) fun hash(node: String, lable: String): String {
839    var prefixNode = node
840    if node.length == 0 {
841      prefixNode = "0000000000000000000000000000000000000000000000000000000000000000"
842    }
843    let lableHash = String.encodeHex(HashAlgorithm.SHA3_256.hash(lable.utf8))
844    let hash = String.encodeHex(HashAlgorithm.SHA3_256.hash(prefixNode.concat(lableHash).utf8))
845    return hash
846  }
847
848  // query domain info by nameHash
849  access(all) fun getDomain(nameHash: String): &Domains.NFT? {
850    let address = Domains.getRecords(nameHash) ?? panic("Domain not exist")
851    let account = getAccount(address)
852    let collection = account.capabilities.borrow<&{Domains.CollectionPublic}>(Domains.CollectionPublicPath)!
853    // let collection = collectionCap.borrow()!
854    var domain: &Domains.NFT? = nil
855
856    let id = Domains.getDomainId(nameHash)
857    domain = collection.borrowDomain(id: id!)
858    return domain
859  }
860
861
862  // Query root domain
863  access(all) fun getRootDomainInfo(domainId: UInt64): RootDomainInfo? {
864    let account = Flowns.account
865    let collection = account.capabilities.borrow<&{Flowns.RootDomainCollectionPublic}>(self.CollectionPublicPath)
866    if collection != nil {
867      return collection!.getDomainInfo(domainId: domainId)
868    }
869    return nil
870  }
871  // Query all root domain
872  access(all) fun getAllRootDomains(): {UInt64: RootDomainInfo}? {
873
874    let account = Flowns.account
875    let rootCollection = account.capabilities.borrow<&{Flowns.RootDomainCollectionPublic}>(self.CollectionPublicPath)
876    if rootCollection != nil  {
877        return rootCollection!.getAllDomains()
878    }
879    return nil
880  }
881  
882  // Check domain available 
883  access(all) fun available(nameHash: String): Bool {
884
885    if Domains.getRecords(nameHash) == nil {
886      return true
887    }
888    return Domains.isExpired(nameHash)
889  }
890
891  access(all) fun getRentPrices(domainId: UInt64): {Int: UFix64} {
892
893    let account = Flowns.account
894    let rootCollection = account.capabilities.borrow<&{Flowns.RootDomainCollectionPublic}>(self.CollectionPublicPath)
895    if rootCollection != nil  {
896      return rootCollection!.getPrices(domainId: domainId)
897    }
898    return {}
899  }
900
901  access(all) fun getRootVaultBalance(domainId: UInt64): UFix64 {
902
903    let account = Flowns.account
904    let rootCollection = account.capabilities.borrow<&{Flowns.RootDomainCollectionPublic}>(self.CollectionPublicPath)
905    // let collection = rootCollectionCap.borrow()?? panic("Could not borrow collection ")
906    let balance = rootCollection!.getVaultBalance(domainId: domainId)
907    return balance
908  }
909
910  access(all) fun registerDomain(domainId: UInt64, name: String, duration: UFix64, feeTokens: @{FungibleToken.Vault}, receiver: Capability<&{NonFungibleToken.Receiver}>, refer: Address? ){
911    pre {
912      Flowns.isPause == false : "Register pause"
913    }
914    let account = Flowns.account
915    let rootCollection = account.capabilities.borrow<&{Flowns.RootDomainCollectionPublic}>(self.CollectionPublicPath)
916    // let collection = rootCollectionCap.borrow() ?? panic("Could not borrow collection ")
917    rootCollection!.registerDomain(domainId: domainId, name: name, duration: duration, feeTokens: <-feeTokens, receiver: receiver, refer: refer)
918  }
919  
920  access(all) fun renewDomain(domainId: UInt64, domain: &Domains.NFT, duration: UFix64, feeTokens: @{FungibleToken.Vault}, refer: Address?) {
921    pre {
922      Flowns.isPause == false : "Renewer pause"
923    }
924    let account = Flowns.account
925    let rootCollection = account.capabilities.borrow<&{Flowns.RootDomainCollectionPublic}>(self.CollectionPublicPath)
926    // let collection = rootCollectionCap.borrow() ?? panic("Could not borrow collection ")
927    rootCollection!.renewDomain(domainId: domainId, domain: domain, duration: duration, feeTokens: <-feeTokens, refer: refer)
928  }
929
930  access(all) fun renewDomainWithNameHash(nameHash: String, duration: UFix64, feeTokens: @{FungibleToken.Vault}, refer: Address?) {
931    pre {
932      Flowns.isPause == false : "Renewer pause"
933      duration > 0.0 : "Duration must great than 0"
934    }
935    
936    let account = Flowns.account
937    let rootCollection = account.capabilities.borrow<&{Flowns.RootDomainCollectionPublic}>(self.CollectionPublicPath)
938    // let collection = rootCollectionCap.borrow() ?? panic("Could not borrow collection ")
939    rootCollection!.renewDomainWithNameHash(nameHash: nameHash, duration: duration, feeTokens: <-feeTokens, refer: refer)
940  }
941  
942  init() {
943
944    self.CollectionPublicPath = /public/flownsCollection
945    self.CollectionStoragePath = /storage/flownsCollection
946    self.FlownsAdminPrivatePath = /private/flownsAdmin
947    self.FlownsAdminStoragePath =/storage/flownsAdmin
948
949    let account = self.account
950    let admin <- Flowns.createAdminClient()
951
952    account.storage.save<@Flowns.Admin>(<-admin, to: self.FlownsAdminStoragePath)
953
954    self.totalRootDomains = 0
955    self.isPause = true
956    self.forbidChars = "!@#$%^&*()<>? ./ABCDEFGHIJKLMNOPQRSTUVWXYZ"
957
958    let collection: @Flowns.RootDomainCollection <- create RootDomainCollection()
959
960    account.storage.save(<-collection, to: Flowns.CollectionStoragePath)
961  }
962}
963