-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathFIND.cdc
1514 lines (1213 loc) · 55.9 KB
/
FIND.cdc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import FungibleToken from "./standard/FungibleToken.cdc"
import FlowToken from "./standard/FlowToken.cdc"
import DapperUtilityCoin from "./standard/DapperUtilityCoin.cdc"
import Profile from "./Profile.cdc"
import Debug from "./Debug.cdc"
import Clock from "./Clock.cdc"
import Sender from "./Sender.cdc"
import ProfileCache from "./ProfileCache.cdc"
import FindUtils from "./FindUtils.cdc"
import PublicPriceOracle from "./community/PublicPriceOracle.cdc"
import FUSD from "./standard/FUSD.cdc"
/*
///FIND
///Flow Integrated Name Directory - A naming service on flow,
/// Lease a name in the network for as little as 5 FUSD a year, (4 characters cost 100, 3 cost 500)
Taxonomy:
- name: a textual description minimum 3 chars long that can be leased in FIND
- profile: A Versus profile that represents a person, a name registed in FIND points to a profile
- lease: a resource representing registering a name for a period of 1 year
- leaseCollection: Collection of the leases an account holds
- leaseStatus: FREE|TAKEN|LOCKED, a LOCKED lease can be reopend by the owner. A lease will be locked for 90 days before it is freed
*/
pub contract FIND {
//event when FT is sent
pub event FungibleTokenSent(from:Address, fromName:String?, name:String, toAddress:Address, message:String, tag:String, amount: UFix64, ftType:String)
/// An event to singla that there is a name in the network
pub event Name(name: String)
pub event AddonActivated(name: String, addon:String)
/// Emitted when a name is registred in FIND
pub event Register(name: String, owner: Address, validUntil: UFix64, lockedUntil: UFix64)
/// Emitted when a name is moved to a new owner
pub event Moved(name: String, previousOwner: Address, newOwner: Address, validUntil: UFix64, lockedUntil: UFix64)
//store the network itself
pub let NetworkStoragePath: StoragePath
pub let NetworkPrivatePath: PrivatePath
//store the leases you own
pub let LeaseStoragePath: StoragePath
pub let LeasePublicPath: PublicPath
pub fun getLeases() : [NetworkLease] {
if let network = self.account.borrow<&Network>(from: FIND.NetworkStoragePath) {
return network.profiles.values
}
panic("Network is not set up")
}
//////////////////////////////////////////
// ORACLE
//////////////////////////////////////////
// Get the latest FLOW/USD price
//This uses the FLOW/USD increment.fi oracle
pub fun getLatestPrice(): UFix64 {
let lastResult = PublicPriceOracle.getLatestPrice(oracleAddr: self.getFlowUSDOracleAddress())
let lastBlockNum = PublicPriceOracle.getLatestBlockHeight(oracleAddr: self.getFlowUSDOracleAddress())
/*
// Make sure the price is not expired
if getCurrentBlock().height - lastBlockNum > 2000 {
panic("Price is expired")
}
*/
return lastResult
}
//TODO we have to make sure this is correct is the price the amount of flow for 1 usd or the other way around
//This uses the FLOW/USD increment.fi oracle
//TODO: We might need some slippage here
// Convert FLOW to USD
pub fun convertFLOWToUSD(_ amount: UFix64): UFix64 {
return amount * self.getLatestPrice()
}
//This uses the FLOW/USD increment.fi oracle
// Convert USD to FLOW
pub fun convertUSDToFLOW(_ amount: UFix64): UFix64 {
return amount / self.getLatestPrice()
}
//////////////////////////////////////////
// HELPER FUNCTIONS
//////////////////////////////////////////
//These methods are basically just here for convenience
/// Calculate the cost of an name
/// @param _ the name to calculate the cost for
pub fun calculateCostInFlow(_ name:String) : UFix64 {
if !FIND.validateFindName(name) {
panic("A FIND name has to be lower-cased alphanumeric or dashes and between 3 and 16 characters")
}
if let network = self.account.borrow<&Network>(from: FIND.NetworkStoragePath) {
return self.convertUSDToFLOW(network.calculateCost(name))
}
panic("Network is not set up")
}
pub fun calculateAddonCostInFlow(_ addon: String) : UFix64 {
let network=FIND.account.borrow<&Network>(from: FIND.NetworkStoragePath)!
if !network.publicEnabled {
panic("Public registration is not enabled yet")
}
if network.addonPrices[addon] == nil {
panic("This addon is not available. addon : ".concat(addon))
}
var addonPrice = network.addonPrices[addon]!
let cost= FIND.convertUSDToFLOW(addonPrice)
return cost
}
pub fun calculateCost(_ name:String) : UFix64 {
if !FIND.validateFindName(name) {
panic("A FIND name has to be lower-cased alphanumeric or dashes and between 3 and 16 characters")
}
if let network = self.account.borrow<&Network>(from: FIND.NetworkStoragePath) {
return network.calculateCost(name)
}
panic("Network is not set up")
}
pub fun resolve(_ input:String) : Address? {
let trimmedInput = FIND.trimFindSuffix(input)
if FIND.validateFindName(trimmedInput) {
if let network = self.account.borrow<&Network>(from: FIND.NetworkStoragePath) {
return network.lookup(trimmedInput)?.owner?.address
}
return nil
}
var address=trimmedInput
if trimmedInput.utf8[1] == 120 {
address = trimmedInput.slice(from: 2, upTo: trimmedInput.length)
}
var r:UInt64 = 0
var bytes = address.decodeHex()
while bytes.length>0{
r = r + (UInt64(bytes.removeFirst()) << UInt64(bytes.length * 8 ))
}
return Address(r)
}
/// Lookup the address registered for a name
pub fun lookupAddress(_ name:String): Address? {
let trimmedName = FIND.trimFindSuffix(name)
if !FIND.validateFindName(trimmedName) {
panic("A FIND name has to be lower-cased alphanumeric or dashes and between 3 and 16 characters")
}
if let network = self.account.borrow<&Network>(from: FIND.NetworkStoragePath) {
return network.lookup(trimmedName)?.owner?.address
}
return nil
}
/// Lookup the profile registered for a name
pub fun lookup(_ input:String): &{Profile.Public}? {
if let address = FIND.resolve(input) {
let account = getAccount(address)
let cap = account.getCapability<&{Profile.Public}>(Profile.publicPath)
if cap.check() {
return cap.borrow()
}
}
return nil
}
pub fun reverseLookupFN() : ((Address) : String?) {
return fun(address:Address): String? {
return FIND.reverseLookup(address)
}
}
/// lookup if an address has a .find name, if it does pick either the default one or the first registered
pub fun reverseLookup(_ address:Address): String? {
let leaseNameCache = ProfileCache.getAddressLeaseName(address)
if leaseNameCache == nil {
let account=getAccount(address)
let leaseCap = account.getCapability<&FIND.LeaseCollection{FIND.LeaseCollectionPublic}>(FIND.LeasePublicPath)
if !leaseCap.check() {
return nil
}
let profileFindName= Profile.find(address).getFindName()
let network = self.account.borrow<&Network>(from: FIND.NetworkStoragePath) ?? panic("Network is not set up")
if profileFindName != "" {
let status = network.readStatus(profileFindName)
if status.owner != nil && status.owner! == address {
if status.status == FIND.LeaseStatus.TAKEN {
ProfileCache.setAddressLeaseNameCache(address: address, leaseName: profileFindName, validUntil: network.getLeaseExpireTime(profileFindName))
return profileFindName
}
}
}
let leaseCol = leaseCap.borrow()!
let nameLeases = leaseCol.getNames()
for nameLease in nameLeases {
//filter out all leases that are FREE or LOCKED since they are not actice
let status = network.readStatus(nameLease)
if status.owner != nil && status.owner! == address {
if status.status == FIND.LeaseStatus.TAKEN {
ProfileCache.setAddressLeaseNameCache(address: address, leaseName: nameLease, validUntil: network.getLeaseExpireTime(nameLease))
return nameLease
}
}
}
ProfileCache.setAddressLeaseNameCache(address: address, leaseName: nil, validUntil: UFix64.max)
return nil
} else if leaseNameCache! == "" {
// If empty string, return no find Name
return nil
}
return leaseNameCache!
}
/// Deposit FT to name
/// @param to: The name to send money too
/// @param message: The message to send
/// @param tag: The tag to add to the event
/// @param vault: The vault to send too
/// @param from: The sender that sent the funds
pub fun depositWithTagAndMessage(to:String, message:String, tag: String, vault: @FungibleToken.Vault, from: &Sender.Token){
let fromAddress= from.owner!.address
let maybeAddress = FIND.resolve(to)
if maybeAddress == nil{
panic("Not a valid .find name or address")
}
let address=maybeAddress!
let account = getAccount(address)
let cap = account.getCapability<&{Profile.Public}>(Profile.publicPath)
if cap.check() {
let profile= cap.borrow()!
emit FungibleTokenSent(from: fromAddress, fromName: FIND.reverseLookup(fromAddress), name: to, toAddress: profile.getAddress(), message:message, tag:tag, amount:vault.balance, ftType:vault.getType().identifier)
profile.deposit(from: <- vault)
return
}
var path = ""
if vault.getType() == Type<@FlowToken.Vault>() {
path ="flowTokenReceiver"
} else {
panic("Could not find a valid receiver for this vault type")
}
if path != "" {
emit FungibleTokenSent(from: fromAddress, fromName: FIND.reverseLookup(fromAddress), name: "", toAddress: address, message:message, tag:tag, amount:vault.balance, ftType:vault.getType().identifier)
account.getCapability<&{FungibleToken.Receiver}>(PublicPath(identifier: path)!).borrow()!.deposit(from: <- vault)
return
}
panic("Could not find a valid receiver for this vault type")
}
/// Deposit FT to name
/// @param to: The name to send money too
/// @param from: The vault to send too
pub fun deposit(to:String, from: @FungibleToken.Vault) {
if !FIND.validateFindName(to) {
panic("A FIND name has to be lower-cased alphanumeric or dashes and between 3 and 16 characters")
}
if let network = self.account.borrow<&Network>(from: FIND.NetworkStoragePath) {
let profile=network.lookup(to) ?? panic("could not find name")
profile.deposit(from: <- from)
return
}
panic("Network is not set up")
}
/// Return the status for a given name
/// @return The Name status of a name
pub fun status(_ name: String): NameStatus {
if !FIND.validateFindName(name) {
panic("A FIND name has to be lower-cased alphanumeric or dashes and between 3 and 16 characters")
}
if let network = self.account.borrow<&Network>(from: FIND.NetworkStoragePath) {
return network.readStatus(name)
}
panic("Network is not set up")
}
/// Struct holding information about a lease. Contains both the internal status the owner of the lease and if the state is persisted or not.
pub struct NameStatus{
pub let status: LeaseStatus
pub let owner: Address?
init(status:LeaseStatus, owner:Address?) {
self.status=status
self.owner=owner
}
}
/*
=============================================================
Lease is a collection/resource for storing the token leases
Also have a seperate Auction for tracking auctioning of leases
=============================================================
*/
/*
Lease is a resource you get back when you register a lease.
You can use methods on it to renew the lease or to move to another profile
*/
pub resource Lease {
access(contract) let name: String
access(contract) let networkCap: Capability<&Network>
access(contract) var addons: {String: Bool}
//These fields are here, but they are deprecated
access(contract) var salePrice: UFix64?
access(contract) var auctionStartPrice: UFix64?
access(contract) var auctionReservePrice: UFix64?
access(contract) var auctionDuration: UFix64
access(contract) var auctionMinBidIncrement: UFix64
access(contract) var auctionExtensionOnLateBid: UFix64
access(contract) var offerCallback: Capability<&BidCollection{BidCollectionPublic}>?
init(name:String, networkCap: Capability<&Network>) {
self.name=name
self.networkCap= networkCap
self.salePrice=nil
self.auctionStartPrice=nil
self.auctionReservePrice=nil
self.auctionDuration=86400.0
self.auctionExtensionOnLateBid=300.0
self.auctionMinBidIncrement=10.0
self.offerCallback=nil
self.addons={}
}
pub fun getName() : String {
return self.name
}
pub fun getAddon() : [String] {
return self.addons.keys
}
pub fun checkAddon(addon: String) : Bool {
if !self.addons.containsKey(addon) {
return false
}
return self.addons[addon]!
}
access(contract) fun addAddon(_ addon:String) {
self.addons[addon]=true
}
//TODO: can we delete some of these method
pub fun setExtentionOnLateBid(_ time: UFix64) {
self.auctionExtensionOnLateBid=time
}
pub fun setAuctionDuration(_ duration: UFix64) {
self.auctionDuration=duration
}
pub fun setSalePrice(_ price: UFix64?) {
self.salePrice=price
}
pub fun setReservePrice(_ price: UFix64?) {
self.auctionReservePrice=price
}
pub fun setMinBidIncrement(_ price: UFix64) {
self.auctionMinBidIncrement=price
}
pub fun setStartAuctionPrice(_ price: UFix64?) {
self.auctionStartPrice=price
}
pub fun setCallback(_ callback: Capability<&BidCollection{BidCollectionPublic}>?) {
self.offerCallback=callback
}
pub fun extendLease(_ vault: @FlowToken.Vault) {
let network= self.networkCap.borrow() ?? panic("The network is not up")
network.renew(name: self.name, vault:<- vault)
}
pub fun extendLeaseDapper(merchAccount: Address, vault: @DapperUtilityCoin.Vault) {
let network= self.networkCap.borrow() ?? panic("The network is not up")
network.renewDapper(merchAccount: merchAccount, name: self.name, vault:<- vault)
}
access(contract) fun move(profile: Capability<&{Profile.Public}>) {
let network= self.networkCap.borrow() ?? panic("The network is not up")
let senderAddress= network.profiles[self.name]!.profile.address
network.move(name: self.name, profile: profile)
// set FindNames
// receiver
let receiver = profile.borrow() ?? panic("The profile capability is invalid")
if receiver.getFindName() == "" {
receiver.setFindName(self.name)
}
// sender
let sender = Profile.find(senderAddress)
if sender.getFindName() == self.name {
let network = FIND.account.borrow<&Network>(from: FIND.NetworkStoragePath) ?? panic("Network is not set up")
let leaseCol = getAccount(senderAddress).getCapability<&FIND.LeaseCollection{FIND.LeaseCollectionPublic}>(FIND.LeasePublicPath).borrow()!
let nameLeases = leaseCol.getNames()
for nameLease in nameLeases {
//filter out all leases that are FREE or LOCKED since they are not actice
let status = network.readStatus(nameLease)
if status.owner != nil && status.owner! == senderAddress {
if status.status == FIND.LeaseStatus.TAKEN {
sender.setFindName(nameLease)
return
}
}
}
sender.setFindName("")
}
}
pub fun getLeaseExpireTime() : UFix64 {
let network = self.networkCap.borrow() ?? panic("The network is not up")
return network.getLeaseExpireTime(self.name)
}
pub fun getLeaseLockedUntil() : UFix64 {
let network = self.networkCap.borrow() ?? panic("The network is not up")
return network.getLeaseLockedUntil(self.name)
}
pub fun getProfile():&{Profile.Public}? {
let network = self.networkCap.borrow() ?? panic("The network is not up")
return network.profile(self.name)
}
pub fun getLeaseStatus() : LeaseStatus {
return FIND.status(self.name).status
}
pub fun validate() : Bool {
// if network is not there anymore, it is not valid
if !self.networkCap.check() {
return false
}
let network = self.networkCap.borrow()!
let lease = network.getLease(self.name)
// if the network lease is nil, it is definitely not validated
if lease == nil {
return false
}
// regardless of the status (FREE / LOCKED / TAKEN)
// (because other functions checks that)
// if this lease is not the current / latest owner, this lease is not valid anymore
let registeredOwner = lease!.profile.address
if registeredOwner == self.owner?.address {
return true
}
return false
}
}
//struct to expose information about leases
pub struct LeaseInformation {
pub let name: String
pub let address: Address
pub let cost: UFix64
pub let status: String
pub let validUntil: UFix64
pub let lockedUntil: UFix64
pub let latestBid: UFix64?
pub let auctionEnds: UFix64?
pub let salePrice: UFix64?
pub let latestBidBy: Address?
pub let currentTime: UFix64
pub let auctionStartPrice: UFix64?
pub let auctionReservePrice: UFix64?
pub let extensionOnLateBid: UFix64?
pub let addons: [String]
init(name: String, status:LeaseStatus, validUntil: UFix64, lockedUntil:UFix64, latestBid: UFix64?, auctionEnds: UFix64?, salePrice: UFix64?, latestBidBy: Address?, auctionStartPrice: UFix64?, auctionReservePrice: UFix64?, extensionOnLateBid:UFix64?, address:Address, addons: [String]){
self.name=name
var s="TAKEN"
if status == LeaseStatus.FREE {
s="FREE"
} else if status == LeaseStatus.LOCKED {
s="LOCKED"
}
self.status=s
self.validUntil=validUntil
self.lockedUntil=lockedUntil
self.latestBid=latestBid
self.latestBidBy=latestBidBy
self.auctionEnds=auctionEnds
self.salePrice=salePrice
self.currentTime=Clock.time()
self.auctionStartPrice=auctionStartPrice
self.auctionReservePrice=auctionReservePrice
self.extensionOnLateBid=extensionOnLateBid
self.address=address
self.cost=FIND.calculateCost(name)
self.addons=addons
}
}
/*
Since a single account can own more then one name there is a collecition of them
This collection has build in support for direct sale of a FIND leaseToken. The network owner till take 2.5% cut
*/
pub resource interface LeaseCollectionPublic {
//fetch all the tokens in the collection
pub fun getLeases(): [String]
pub fun getInvalidatedLeases(): [String]
//fetch all names that are for sale
pub fun getLeaseInformation() : [LeaseInformation]
pub fun getLease(_ name: String) :LeaseInformation?
access(contract) fun deposit(token: @FIND.Lease)
pub fun buyAddon(name:String, addon: String, vault: @FlowToken.Vault)
pub fun buyAddonDapper(merchAccount: Address, name:String, addon:String, vault: @DapperUtilityCoin.Vault)
access(account) fun adminAddAddon(name:String, addon: String)
pub fun getAddon(name:String) : [String]
pub fun checkAddon(name:String, addon: String) : Bool
access(account) fun getNames() : [String]
access(account) fun containsName(_ name: String) : Bool
access(account) fun move(name: String, profile: Capability<&{Profile.Public}>, to: Capability<&LeaseCollection{LeaseCollectionPublic}>)
pub fun getLeaseUUID(_ name: String) : UInt64
}
pub resource LeaseCollection: LeaseCollectionPublic {
// dictionary of NFT conforming tokens
// NFT is a resource type with an `UInt64` ID field
access(contract) var leases: @{String: FIND.Lease}
access(contract) var auctions: @{String: Auction}
//the cut the network will take, default 2.5%
access(contract) let networkCut: UFix64
//the wallet of the network to transfer royalty to
access(contract) let networkWallet: Capability<&{FungibleToken.Receiver}>
init (networkCut: UFix64, networkWallet: Capability<&{FungibleToken.Receiver}>) {
self.leases <- {}
self.auctions <- {}
self.networkCut=networkCut
self.networkWallet=networkWallet
}
pub fun buyAddon(name:String, addon:String, vault: @FlowToken.Vault) {
if !self.leases.containsKey(name) {
panic("Invalid name=".concat(name))
}
let cost = FIND.calculateAddonCostInFlow(addon)
let lease = self.borrow(name)
if !lease.validate() {
panic("This is not a valid lease. Lease already expires and some other user registered it. Lease : ".concat(name))
}
if lease.addons.containsKey(addon) {
panic("You already have this addon : ".concat(addon))
}
if vault.balance != cost {
panic("Expect ".concat(cost.toString()).concat(" FLOW for ").concat(addon).concat(" addon"))
}
lease.addAddon(addon)
//put something in your storage
emit AddonActivated(name: name, addon: addon)
let networkWallet = self.networkWallet.borrow() ?? panic("The network is not up")
networkWallet.deposit(from: <- vault)
}
pub fun buyAddonDapper(merchAccount: Address, name:String, addon:String, vault: @DapperUtilityCoin.Vault) {
FIND.checkMerchantAddress(merchAccount)
if !self.leases.containsKey(name) {
panic("Invalid name=".concat(name))
}
let network=FIND.account.borrow<&Network>(from: FIND.NetworkStoragePath)!
if !network.publicEnabled {
panic("Public registration is not enabled yet")
}
if network.addonPrices[addon] == nil {
panic("This addon is not available. addon : ".concat(addon))
}
let addonPrice = network.addonPrices[addon]!
let lease = self.borrow(name)
if !lease.validate() {
panic("This is not a valid lease. Lease already expires and some other user registered it. Lease : ".concat(name))
}
if lease.addons.containsKey(addon) {
panic("You already have this addon : ".concat(addon))
}
if vault.balance != addonPrice {
panic("Expect ".concat(addonPrice.toString()).concat(" Dapper Credit for ").concat(addon).concat(" addon"))
}
lease.addAddon(addon)
//put something in your storage
emit AddonActivated(name: name, addon: addon)
// This is here just to check if the network is up
let networkWallet = self.networkWallet.borrow() ?? panic("The network is not up")
let wallet = getAccount(merchAccount).getCapability<&{FungibleToken.Receiver}>(/public/dapperUtilityCoinReceiver)
let walletRef = wallet.borrow() ?? panic("Cannot borrow reference to Dapper Merch Account receiver. Address : ".concat(merchAccount.toString()))
walletRef.deposit(from: <- vault)
}
access(account) fun adminAddAddon(name:String, addon:String) {
if !self.leases.containsKey(name) {
panic("Invalid name=".concat(name))
}
let network=FIND.account.borrow<&Network>(from: FIND.NetworkStoragePath)!
if !network.publicEnabled {
panic("Public registration is not enabled yet")
}
if network.addonPrices[addon] == nil {
panic("This addon is not available. addon : ".concat(addon))
}
let addonPrice = network.addonPrices[addon]!
let lease = self.borrow(name)
if !lease.validate() {
panic("This is not a valid lease. Lease already expires and some other user registered it. Lease : ".concat(name))
}
if lease.addons.containsKey(addon) {
panic("You already have this addon : ".concat(addon))
}
lease.addAddon(addon)
//put something in your storage
emit AddonActivated(name: name, addon: addon)
}
pub fun getAddon(name: String) : [String] {
let lease = self.borrow(name)
if !lease.validate() {
return []
}
return lease.getAddon()
}
pub fun checkAddon(name:String, addon: String) : Bool {
let lease = self.borrow(name)
if !lease.validate() {
return false
}
return lease.checkAddon(addon: addon)
}
pub fun getLeaseUUID(_ name: String) : UInt64 {
return self.borrow(name).uuid
}
pub fun getLease(_ name: String) : LeaseInformation? {
if !self.leases.containsKey(name) {
return nil
}
let token=self.borrow(name)
if !token.validate() {
return nil
}
var latestBid: UFix64? = nil
var auctionEnds: UFix64?= nil
var latestBidBy: Address?=nil
return LeaseInformation(name: name, status: token.getLeaseStatus(), validUntil: token.getLeaseExpireTime(), lockedUntil: token.getLeaseLockedUntil(), latestBid: latestBid, auctionEnds: auctionEnds, salePrice: token.salePrice, latestBidBy: latestBidBy, auctionStartPrice: token.auctionStartPrice, auctionReservePrice: token.auctionReservePrice, extensionOnLateBid: token.auctionExtensionOnLateBid, address: token.owner!.address, addons: token.addons.keys)
}
access(account) fun getNames() : [String] {
return self.leases.keys
}
access(account) fun containsName(_ name: String) : Bool {
return self.leases.containsKey(name)
}
pub fun getLeaseInformation() : [LeaseInformation] {
var info: [LeaseInformation]=[]
for name in self.leases.keys {
// if !FIND.validateFindName(name) {
// continue
// }
let lease=self.getLease(name)
if lease != nil && lease!.status != "FREE" {
info.append(lease!)
}
}
return info
}
//note that when moving a name
pub fun move(name: String, profile: Capability<&{Profile.Public}>, to: Capability<&LeaseCollection{LeaseCollectionPublic}>) {
let lease = self.borrow(name)
if !lease.validate() {
panic("This is not a valid lease. Lease already expires and some other user registered it. Lease : ".concat(name))
}
let token <- self.leases.remove(key: name) ?? panic("missing NFT")
emit Moved(name: name, previousOwner:self.owner!.address, newOwner: profile.address, validUntil: token.getLeaseExpireTime(), lockedUntil: token.getLeaseLockedUntil())
token.move(profile: profile)
let walletRef = to.borrow() ?? panic("The receiver capability is not valid. wallet address : ".concat(to.address.toString()))
walletRef.deposit(token: <- token)
}
//depoit a lease token into the lease collection, not available from the outside
access(contract) fun deposit(token: @FIND.Lease) {
// add the new token to the dictionary which removes the old one
let oldToken <- self.leases[token.name] <- token
destroy oldToken
}
// getIDs returns an array of the IDs that are in the collection
pub fun getLeases(): [String] {
var list : [String] = []
for key in self.leases.keys {
let lease = self.borrow(key)
if !lease.validate() {
continue
}
list.append(key)
}
return list
}
pub fun getInvalidatedLeases(): [String] {
var list : [String] = []
for key in self.leases.keys {
let lease = self.borrow(key)
if lease.validate() {
continue
}
list.append(key)
}
return list
}
// borrowNFT gets a reference to an NFT in the collection
// so that the caller can read its metadata and call its methods
pub fun borrow(_ name: String): &FIND.Lease {
return (&self.leases[name] as &FIND.Lease?)!
}
//TODO: remove
//borrow the auction
pub fun borrowAuction(_ name: String): &FIND.Auction {
return (&self.auctions[name] as &FIND.Auction?)!
}
//This has to be here since you can only get this from a auth account and thus we ensure that you cannot use wrong paths
pub fun register(name: String, vault: @FlowToken.Vault){
let profileCap = self.owner!.getCapability<&{Profile.Public}>(Profile.publicPath)
let leases= self.owner!.getCapability<&LeaseCollection{LeaseCollectionPublic}>(FIND.LeasePublicPath)
let network=FIND.account.borrow<&Network>(from: FIND.NetworkStoragePath)!
if !network.publicEnabled {
panic("Public registration is not enabled yet")
}
network.register(name:name, vault: <- vault, profile: profileCap, leases: leases)
}
//This has to be here since you can only get this from a auth account and thus we ensure that you cannot use wrong paths
pub fun registerDapper(merchAccount: Address, name: String, vault: @DapperUtilityCoin.Vault){
let profileCap = self.owner!.getCapability<&{Profile.Public}>(Profile.publicPath)
let leases= self.owner!.getCapability<&LeaseCollection{LeaseCollectionPublic}>(FIND.LeasePublicPath)
let network=FIND.account.borrow<&Network>(from: FIND.NetworkStoragePath)!
if !network.publicEnabled {
panic("Public registration is not enabled yet")
}
network.registerDapper(merchAccount: merchAccount, name:name, vault: <- vault, profile: profileCap, leases: leases)
}
pub fun cleanUpInvalidatedLease(_ name: String) {
let lease = self.borrow(name)
if lease.validate() {
panic("This is a valid lease. You cannot clean this up. Lease : ".concat(name))
}
destroy <- self.leases.remove(key: name)!
}
destroy() {
destroy self.leases
destroy self.auctions
}
}
//Create an empty lease collection that store your leases to a name
pub fun createEmptyLeaseCollection(): @FIND.LeaseCollection {
if let network = self.account.borrow<&Network>(from: FIND.NetworkStoragePath) {
return <- create LeaseCollection(networkCut:network.secondaryCut, networkWallet: network.wallet)
}
panic("Network is not set up")
}
/*
Core network things
//===================================================================================================================
*/
//a struct that represents a lease of a name in the network.
pub struct NetworkLease {
pub let registeredTime: UFix64
pub var validUntil: UFix64
pub var lockedUntil: UFix64
pub(set) var profile: Capability<&{Profile.Public}>
// This address is wrong for some account and can never be refered
pub var address: Address
pub var name: String
init( validUntil:UFix64, lockedUntil:UFix64, profile: Capability<&{Profile.Public}>, name: String) {
self.validUntil=validUntil
self.lockedUntil=lockedUntil
self.registeredTime=Clock.time()
self.profile=profile
self.address= profile.address
self.name=name
}
pub fun setValidUntil(_ unit: UFix64) {
self.validUntil=unit
}
pub fun setLockedUntil(_ unit: UFix64) {
self.lockedUntil=unit
}
pub fun status() : LeaseStatus {
let time=Clock.time()
if time >= self.lockedUntil {
return LeaseStatus.FREE
}
if time >= self.validUntil {
return LeaseStatus.LOCKED
}
return LeaseStatus.TAKEN
}
}
/*
FREE, does not exist in profiles dictionary
TAKEN, registered with a time that is currentTime + leasePeriod
LOCKED, after TAKEN.time you will get a new status and the new time will be
*/
pub enum LeaseStatus: UInt8 {
pub case FREE
pub case TAKEN
pub case LOCKED
}
/*
The main network resource that holds the state of the names in the network
*/
pub resource Network {
access(contract) var wallet: Capability<&{FungibleToken.Receiver}>
access(contract) let leasePeriod: UFix64
access(contract) let lockPeriod: UFix64
access(contract) var defaultPrice: UFix64
access(contract) let secondaryCut: UFix64
access(contract) var pricesChangedAt: UFix64
access(contract) var lengthPrices: {Int: UFix64}
access(contract) var addonPrices: {String: UFix64}
access(contract) var publicEnabled: Bool
//map from name to lease for that name
access(contract) let profiles: { String: NetworkLease}
init(leasePeriod: UFix64, lockPeriod: UFix64, secondaryCut: UFix64, defaultPrice: UFix64, lengthPrices: {Int:UFix64}, wallet:Capability<&{FungibleToken.Receiver}>, publicEnabled:Bool) {
self.leasePeriod=leasePeriod
self.addonPrices = {
"forge" : 50.0 , // will have to run transactions on this when update on mainnet.
"premiumForge" : 1000.0
}
self.lockPeriod=lockPeriod
self.secondaryCut=secondaryCut
self.defaultPrice=defaultPrice
self.lengthPrices=lengthPrices
self.profiles={}
self.wallet=wallet
self.pricesChangedAt= Clock.time()
self.publicEnabled=publicEnabled
}
pub fun getLease(_ name: String) : NetworkLease? {
return self.profiles[name]
}
pub fun setAddonPrice(name:String, price:UFix64) {
self.addonPrices[name]=price
}
pub fun setPrice(default: UFix64, additionalPrices: {Int: UFix64}) {
self.defaultPrice=default
self.lengthPrices=additionalPrices
}
//this method is only called from a lease, and only the owner has that capability
access(contract) fun renew(name: String, vault: @FlowToken.Vault) {
if let lease= self.profiles[name] {
let cost= FIND.calculateCostInFlow(name)
if vault.balance != cost {
panic("Vault did not contain ".concat(cost.toString()).concat(" amount of Flow"))
}
let walletRef = self.wallet.borrow() ?? panic("The receiver capability is invalid. Wallet address : ".concat(self.wallet.address.toString()))
walletRef.deposit(from: <- vault)
self.internal_renew(name: name)
return
}
panic("Could not find profile with name=".concat(name))
}