Smart Contract
Domains
A.233eb012d34b0070.Domains
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