Smart Contract
FIND
A.097bafa4e0b48eef.FIND
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import DapperUtilityCoin from 0xead892083b3e2c6c
4import Profile from 0x097bafa4e0b48eef
5import Debug from 0x097bafa4e0b48eef
6import Clock from 0x097bafa4e0b48eef
7import Sender from 0x097bafa4e0b48eef
8import ProfileCache from 0x097bafa4e0b48eef
9import FindUtils from 0x097bafa4e0b48eef
10import PublicPriceOracle from 0xec67451f8a58216a
11import BandOracle from 0x6801a6222ebf784a
12
13/*
14///FIND
15
16///Flow Integrated Name Directory - A naming service on flow,
17
18/// Lease a name in the network for as little as 5 USD a year, (4 characters cost 100, 3 cost 500)
19
20Taxonomy:
21
22- name: a textual description minimum 3 chars long that can be leased in FIND
23- profile: A Versus profile that represents a person, a name registed in FIND points to a profile
24- lease: a resource representing registering a name for a period of 1 year
25- leaseCollection: Collection of the leases an account holds
26- 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
27*/
28access(all) contract FIND {
29 //event when FT is sent
30 access(all) event FungibleTokenSent(from:Address, fromName:String?, name:String, toAddress:Address, message:String, tag:String, amount: UFix64, ftType:String)
31
32 /// An event to singla that there is a name in the network
33 access(all) event Name(name: String)
34
35 access(all) event AddonActivated(name: String, addon:String)
36
37 /// Emitted when a name is registred in FIND
38 access(all) event Register(name: String, owner: Address, validUntil: UFix64, lockedUntil: UFix64)
39
40 /// Emitted when a name is moved to a new owner
41 access(all) event Moved(name: String, previousOwner: Address, newOwner: Address, validUntil: UFix64, lockedUntil: UFix64)
42
43 //store the network itself
44 access(all) let NetworkStoragePath: StoragePath
45
46 //store the leases you own
47 access(all) let LeaseStoragePath: StoragePath
48 access(all) let LeasePublicPath: PublicPath
49
50 access(all) fun getLeases() : &[NetworkLease] {
51 if let network = self.account.storage.borrow<&Network>(from: FIND.NetworkStoragePath) {
52 return network.profiles.values
53 }
54 panic("Network is not set up")
55 }
56
57 //////////////////////////////////////////
58 // ORACLE
59 //////////////////////////////////////////
60 // Get the latest FLOW/USD price
61 //This uses the FLOW/USD increment.fi oracle
62 access(all) fun getLatestPrice(): UFix64 {
63 let lastResult = PublicPriceOracle.getLatestPrice(oracleAddr: self.getFlowUSDOracleAddress())
64 let lastBlockNum = PublicPriceOracle.getLatestBlockHeight(oracleAddr: self.getFlowUSDOracleAddress())
65
66 // Make sure the price is not expired
67 if getCurrentBlock().height - lastBlockNum > 2000 {
68 return FIND.getLatestPriceBand()
69 }
70
71 return lastResult
72 }
73
74 //this uses band oracle, we use this as backup
75 access(all) fun getLatestPriceBand(): UFix64 {
76
77 let acct = FIND.account
78 let vaultRef = acct.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault) ?? panic("Cannot borrow reference to signer's FLOW vault")
79 let payment <- vaultRef.withdraw(amount: BandOracle.getFee())
80 let baseSymbol="FLOW"
81 let quoteSymbol="USDC"
82 let quote =BandOracle.getReferenceData (baseSymbol: baseSymbol, quoteSymbol: quoteSymbol, payment: <- payment)
83
84 return quote.fixedPointRate
85 }
86
87
88
89 access(all) fun convertFLOWToUSD(_ amount: UFix64): UFix64 {
90 return amount * self.getLatestPrice()
91 }
92
93 access(all) fun convertUSDToFLOW(_ amount: UFix64): UFix64 {
94 return amount / self.getLatestPrice()
95 }
96
97 //////////////////////////////////////////
98 // HELPER FUNCTIONS
99 //////////////////////////////////////////
100
101 //These methods are basically just here for convenience
102
103 access(all) fun calculateCostInFlow(_ name:String) : UFix64 {
104 if !FIND.validateFindName(name) {
105 panic("A FIND name has to be lower-cased alphanumeric or dashes and between 3 and 16 characters")
106 }
107
108 if let network = FIND.account.storage.borrow<&Network>(from: FIND.NetworkStoragePath) {
109 return self.convertUSDToFLOW(network.calculateCost(name))
110 }
111 panic("Network is not set up")
112 }
113
114 access(all) fun calculateAddonCostInFlow(_ addon: String) : UFix64 {
115 let network=FIND.account.storage.borrow<&Network>(from: FIND.NetworkStoragePath)!
116
117 if !network.publicEnabled {
118 panic("Public registration is not enabled yet")
119 }
120
121 if network.addonPrices[addon] == nil {
122 panic("This addon is not available. addon : ".concat(addon))
123 }
124 var addonPrice = network.addonPrices[addon]!
125 let cost= FIND.convertUSDToFLOW(addonPrice)
126 return cost
127 }
128
129 /// Calculate the cost of an name
130 /// @param _ the name to calculate the cost for
131 access(all) fun calculateCost(_ name:String) : UFix64 {
132 if !FIND.validateFindName(name) {
133 panic("A FIND name has to be lower-cased alphanumeric or dashes and between 3 and 16 characters")
134 }
135
136 if let network = self.account.storage.borrow<&Network>(from: FIND.NetworkStoragePath) {
137 return network.calculateCost(name)
138 }
139 panic("Network is not set up")
140 }
141
142 access(all) fun resolve(_ input:String) : Address? {
143
144 let trimmedInput = FIND.trimFindSuffix(input)
145
146 if FIND.validateFindName(trimmedInput) {
147 if let network = self.account.storage.borrow<&Network>(from: FIND.NetworkStoragePath) {
148 return network.lookup(trimmedInput)?.owner?.address
149 }
150
151 return nil
152 }
153
154 var address=trimmedInput
155 if trimmedInput.utf8[1] == 120 {
156 address = trimmedInput.slice(from: 2, upTo: trimmedInput.length)
157 }
158 var r:UInt64 = 0
159 var bytes = address.decodeHex()
160
161 while bytes.length>0{
162 r = r + (UInt64(bytes.removeFirst()) << UInt64(bytes.length * 8 ))
163 }
164
165 return Address(r)
166 }
167
168 /// Lookup the address registered for a name
169 access(all) fun lookupAddress(_ name:String): Address? {
170
171 let trimmedName = FIND.trimFindSuffix(name)
172
173 if !FIND.validateFindName(trimmedName) {
174 panic("A FIND name has to be lower-cased alphanumeric or dashes and between 3 and 16 characters")
175 }
176
177 if let network = self.account.storage.borrow<&Network>(from: FIND.NetworkStoragePath) {
178 return network.lookup(trimmedName)?.owner?.address
179 }
180 return nil
181 }
182
183 /// Lookup the profile registered for a name
184 access(all) fun lookup(_ input:String): &{Profile.Public}? {
185 if let address = FIND.resolve(input) {
186 let account = getAccount(address)
187 return account.capabilities.borrow<&{Profile.Public}>(Profile.publicPath)
188 }
189 return nil
190 }
191
192 access(all) fun reverseLookupFN() : fun(Address) : String? {
193 return fun(address:Address): String? {
194 return FIND.reverseLookup(address)
195 }
196 }
197
198 /// lookup if an address has a .find name, if it does pick either the default one or the first registered
199 access(all) fun reverseLookup(_ address:Address): String? {
200
201 let leaseNameCache = ProfileCache.getAddressLeaseName(address)
202
203 if leaseNameCache == nil {
204 let leaseOptCol = getAccount(address).capabilities.borrow<&{FIND.LeaseCollectionPublic}>(FIND.LeasePublicPath)
205
206 if leaseOptCol == nil {
207 return nil
208 }
209
210 let profileFindName= Profile.find(address).getFindName()
211
212 let network = self.account.storage.borrow<&Network>(from: FIND.NetworkStoragePath) ?? panic("Network is not set up")
213
214 if profileFindName != "" {
215 let status = network.readStatus(profileFindName)
216 if status.owner != nil && status.owner! == address {
217 if status.status == FIND.LeaseStatus.TAKEN {
218 ProfileCache.setAddressLeaseNameCache(address: address, leaseName: profileFindName, validUntil: network.getLeaseExpireTime(profileFindName))
219 return profileFindName
220 }
221 }
222 }
223
224 let leaseCol = leaseOptCol!
225 let nameLeases = leaseCol.getNames()
226 for nameLease in nameLeases {
227
228 //filter out all leases that are FREE or LOCKED since they are not actice
229 let status = network.readStatus(nameLease)
230 if status.owner != nil && status.owner! == address {
231 if status.status == FIND.LeaseStatus.TAKEN {
232 ProfileCache.setAddressLeaseNameCache(address: address, leaseName: nameLease, validUntil: network.getLeaseExpireTime(nameLease))
233 return nameLease
234 }
235 }
236 }
237 ProfileCache.setAddressLeaseNameCache(address: address, leaseName: nil, validUntil: UFix64.max)
238 return nil
239 } else if leaseNameCache! == "" {
240 // If empty string, return no find Name
241 return nil
242 }
243 return leaseNameCache!
244 }
245
246 /// Deposit FT to name
247 /// @param to: The name to send money too
248 /// @param message: The message to send
249 /// @param tag: The tag to add to the event
250 /// @param vault: The vault to send too
251 /// @param from: The sender that sent the funds
252 access(all) fun depositWithTagAndMessage(to:String, message:String, tag: String, vault: @{FungibleToken.Vault}, from: &Sender.Token){
253
254 let fromAddress= from.owner!.address
255 let maybeAddress = FIND.resolve(to)
256 if maybeAddress == nil{
257 panic("Not a valid .find name or address")
258 }
259 let address=maybeAddress!
260
261 let account = getAccount(address)
262 if let profile = account.capabilities.borrow<&{Profile.Public}>(Profile.publicPath) {
263 emit FungibleTokenSent(from: fromAddress, fromName: FIND.reverseLookup(fromAddress), name: to, toAddress: profile.getAddress(), message:message, tag:tag, amount:vault.balance, ftType:vault.getType().identifier)
264 profile.deposit(from: <- vault)
265 return
266 }
267
268 var path = ""
269 if vault.getType() == Type<@FlowToken.Vault>() {
270 path ="flowTokenReceiver"
271 } else {
272 panic("Could not find a valid receiver for this vault type")
273 }
274 if path != "" {
275 emit FungibleTokenSent(from: fromAddress, fromName: FIND.reverseLookup(fromAddress), name: "", toAddress: address, message:message, tag:tag, amount:vault.balance, ftType:vault.getType().identifier)
276 account.capabilities.borrow<&{FungibleToken.Receiver}>(PublicPath(identifier: path)!)!.deposit(from: <- vault)
277 return
278 }
279 panic("Could not find a valid receiver for this vault type")
280
281 }
282
283
284 /// Deposit FT to name
285 /// @param to: The name to send money too
286 /// @param from: The vault to send too
287 access(all) fun deposit(to:String, from: @{FungibleToken.Vault}) {
288 if !FIND.validateFindName(to) {
289 panic("A FIND name has to be lower-cased alphanumeric or dashes and between 3 and 16 characters")
290 }
291
292 if let network = self.account.storage.borrow<&Network>(from: FIND.NetworkStoragePath) {
293 let profile=network.lookup(to) ?? panic("could not find name")
294 profile.deposit(from: <- from)
295 return
296 }
297 panic("Network is not set up")
298 }
299
300 /// Return the status for a given name
301 /// @return The Name status of a name
302 access(all) fun status(_ name: String): NameStatus {
303 if !FIND.validateFindName(name) {
304 panic("A FIND name has to be lower-cased alphanumeric or dashes and between 3 and 16 characters")
305 }
306
307 if let network = self.account.storage.borrow<&Network>(from: FIND.NetworkStoragePath) {
308 return network.readStatus(name)
309 }
310 panic("Network is not set up")
311 }
312
313
314 /// Struct holding information about a lease. Contains both the internal status the owner of the lease and if the state is persisted or not.
315 access(all) struct NameStatus{
316 access(all) let status: LeaseStatus
317 access(all) let owner: Address?
318
319 init(status:LeaseStatus, owner:Address?) {
320 self.status=status
321 self.owner=owner
322 }
323 }
324
325
326
327 /*
328 =============================================================
329 Lease is a collection/resource for storing the token leases
330 Also have a seperate Auction for tracking auctioning of leases
331 =============================================================
332 */
333
334 access(all) entitlement LeaseOwner
335
336 /*
337
338 Lease is a resource you get back when you register a lease.
339 You can use methods on it to renew the lease or to move to another profile
340 */
341 access(all) resource Lease {
342 access(contract) let name: String
343 access(contract) let networkCap: Capability<&Network>
344 access(contract) var addons: {String: Bool}
345
346 //These fields are here, but they are not in use anymore
347 access(contract) var salePrice: UFix64?
348 access(contract) var auctionStartPrice: UFix64?
349 access(contract) var auctionReservePrice: UFix64?
350 access(contract) var auctionDuration: UFix64
351 access(contract) var auctionMinBidIncrement: UFix64
352 access(contract) var auctionExtensionOnLateBid: UFix64
353 access(contract) var offerCallback: Capability<&BidCollection>?
354
355 init(name:String, networkCap: Capability<&Network>) {
356 self.name=name
357 self.networkCap= networkCap
358 self.salePrice=nil
359 self.auctionStartPrice=nil
360 self.auctionReservePrice=nil
361 self.auctionDuration=86400.0
362 self.auctionExtensionOnLateBid=300.0
363 self.auctionMinBidIncrement=10.0
364 self.offerCallback=nil
365 self.addons={}
366 }
367
368 access(all) fun getName() : String {
369 return self.name
370 }
371
372 access(all) fun getAddon() : [String] {
373 return self.addons.keys
374 }
375
376 access(all) fun checkAddon(addon: String) : Bool {
377 if !self.addons.containsKey(addon) {
378 return false
379 }
380 return self.addons[addon]!
381 }
382
383 access(contract) fun addAddon(_ addon:String) {
384 self.addons[addon]=true
385 }
386
387 access(LeaseOwner) fun extendLease(_ vault: @FlowToken.Vault) {
388 let network= self.networkCap.borrow() ?? panic("The network is not up")
389 network.renew(name: self.name, vault:<- vault)
390 }
391
392 access(LeaseOwner) fun extendLeaseDapper(merchAccount: Address, vault: @DapperUtilityCoin.Vault) {
393 let network= self.networkCap.borrow() ?? panic("The network is not up")
394 network.renewDapper(merchAccount: merchAccount, name: self.name, vault:<- vault)
395 }
396
397 access(contract) fun move(profile: Capability<&{Profile.Public}>) {
398 let network= self.networkCap.borrow() ?? panic("The network is not up")
399 let senderAddress= network.profiles[self.name]!.profile.address
400 network.move(name: self.name, profile: profile)
401
402
403 // set FindNames
404 // receiver
405 let receiver = profile.borrow() ?? panic("The profile capability is invalid")
406 if receiver.getFindName() == "" {
407 receiver.setFindName(self.name)
408 }
409
410 // sender
411 let sender = Profile.find(senderAddress)
412 if sender.getFindName() == self.name {
413 let network = FIND.account.storage.borrow<&Network>(from: FIND.NetworkStoragePath) ?? panic("Network is not set up")
414 let leaseCol = getAccount(senderAddress).capabilities.borrow<&{FIND.LeaseCollectionPublic}>(FIND.LeasePublicPath) ?? panic("Could not borrow lease collection")
415
416 let nameLeases = leaseCol.getNames()
417 for nameLease in nameLeases {
418
419 //filter out all leases that are FREE or LOCKED since they are not actice
420 let status = network.readStatus(nameLease)
421 if status.owner != nil && status.owner! == senderAddress {
422 if status.status == FIND.LeaseStatus.TAKEN {
423 sender.setFindName(nameLease)
424 return
425 }
426 }
427
428 }
429 sender.setFindName("")
430 }
431 }
432
433 access(all) fun getLeaseExpireTime() : UFix64 {
434 let network = self.networkCap.borrow() ?? panic("The network is not up")
435 return network.getLeaseExpireTime(self.name)
436 }
437
438 access(all) fun getLeaseLockedUntil() : UFix64 {
439 let network = self.networkCap.borrow() ?? panic("The network is not up")
440 return network.getLeaseLockedUntil(self.name)
441 }
442
443 access(all) fun getProfile():&{Profile.Public}? {
444 let network = self.networkCap.borrow() ?? panic("The network is not up")
445 return network.profile(self.name)
446 }
447
448 access(all) fun getLeaseStatus() : LeaseStatus {
449 return FIND.status(self.name).status
450 }
451
452 access(all) fun validate() : Bool {
453 // if network is not there anymore, it is not valid
454 if !self.networkCap.check() {
455 return false
456 }
457 let network = self.networkCap.borrow()!
458 let lease = network.getLease(self.name)
459 // if the network lease is nil, it is definitely not validated
460 if lease == nil {
461 Debug.log("no lease")
462 return false
463 }
464
465 // regardless of the status (FREE / LOCKED / TAKEN)
466 //TODO: these comments here are wrong...
467 // (because other functions checks that)
468 // if this lease is not the current / latest owner, this lease is not valid anymore
469 let registeredOwner = lease!.profile.address
470 if registeredOwner == self.owner?.address {
471 if lease!.status() == LeaseStatus.FREE {
472 return false
473 }
474 return true
475 }
476
477 return false
478 }
479 }
480
481
482
483 //struct to expose information about leases
484 access(all) struct LeaseInformation {
485 access(all) let name: String
486 access(all) let address: Address
487 access(all) let cost: UFix64
488 access(all) let status: String
489 access(all) let validUntil: UFix64
490 access(all) let lockedUntil: UFix64
491 access(all) let latestBid: UFix64?
492 access(all) let auctionEnds: UFix64?
493 access(all) let salePrice: UFix64?
494 access(all) let latestBidBy: Address?
495 access(all) let currentTime: UFix64
496 access(all) let auctionStartPrice: UFix64?
497 access(all) let auctionReservePrice: UFix64?
498 access(all) let extensionOnLateBid: UFix64?
499 access(all) let addons: [String]
500
501 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]){
502
503 self.name=name
504 var s="TAKEN"
505 if status == LeaseStatus.FREE {
506 s="FREE"
507 } else if status == LeaseStatus.LOCKED {
508 s="LOCKED"
509 }
510 self.status=s
511 self.validUntil=validUntil
512 self.lockedUntil=lockedUntil
513 self.latestBid=latestBid
514 self.latestBidBy=latestBidBy
515 self.auctionEnds=auctionEnds
516 self.salePrice=salePrice
517 self.currentTime=Clock.time()
518 self.auctionStartPrice=auctionStartPrice
519 self.auctionReservePrice=auctionReservePrice
520 self.extensionOnLateBid=extensionOnLateBid
521 self.address=address
522 self.cost=FIND.calculateCost(name)
523 self.addons=addons
524
525 }
526 access(all) fun getAddons() : [String] {
527 return self.addons
528 }
529
530 }
531 /*
532 Since a single account can own more then one name there is a collecition of them
533 This collection has build in support for direct sale of a FIND leaseToken. The network owner till take 2.5% cut
534 */
535 access(all) resource interface LeaseCollectionPublic {
536 //fetch all the tokens in the collection
537 access(all) fun getLeases(): [String]
538 access(all) fun getInvalidatedLeases(): [String]
539 //fetch all names that are for sale
540 access(all) fun getLeaseInformation() : [LeaseInformation]
541 access(all) fun getLease(_ name: String) :LeaseInformation?
542
543 //add a new lease token to the collection, can only be called in this contract
544 access(contract) fun deposit(token: @FIND.Lease)
545
546 access(all) fun buyAddon(name:String, addon: String, vault: @FlowToken.Vault)
547 access(all) fun buyAddonDapper(merchAccount: Address, name:String, addon:String, vault: @DapperUtilityCoin.Vault)
548 access(account) fun adminAddAddon(name:String, addon: String)
549 access(all) fun getAddon(name:String) : [String]
550 access(all) fun checkAddon(name:String, addon: String) : Bool
551 access(account) fun getNames() : [String]
552 access(account) fun containsName(_ name: String) : Bool
553 access(LeaseOwner) fun move(name: String, profile: Capability<&{Profile.Public}>, to: Capability<&LeaseCollection>)
554 access(all) fun getLeaseUUID(_ name: String) : UInt64
555 }
556
557 access(all) resource LeaseCollection: LeaseCollectionPublic {
558 // dictionary of NFT conforming tokens
559 // NFT is a resource type with an `UInt64` ID field
560 access(contract) var leases: @{String: FIND.Lease}
561 access(contract) var auctions: @{String: Auction}
562
563 //the cut the network will take, default 2.5%
564 access(contract) let networkCut: UFix64
565
566 //the wallet of the network to transfer royalty to
567 access(contract) let networkWallet: Capability<&{FungibleToken.Receiver}>
568
569 init (networkCut: UFix64, networkWallet: Capability<&{FungibleToken.Receiver}>) {
570 self.leases <- {}
571 self.auctions <- {}
572 self.networkCut=networkCut
573 self.networkWallet=networkWallet
574 }
575
576 access(all) fun buyAddon(name:String, addon:String, vault: @FlowToken.Vault) {
577 if !self.leases.containsKey(name) {
578 panic("Invalid name=".concat(name))
579 }
580 let cost=FIND.calculateAddonCostInFlow(addon)
581
582 let lease = self.borrowAuth(name)
583
584 if !lease.validate() {
585 panic("This is not a valid lease. Lease already expires and some other user registered it. Lease : ".concat(name))
586 }
587
588 if lease.addons.containsKey(addon) {
589 panic("You already have this addon : ".concat(addon))
590 }
591
592 if vault.balance != cost {
593 panic("Expect ".concat(cost.toString()).concat(" FLOW for ").concat(addon).concat(" addon"))
594 }
595
596 lease.addAddon(addon)
597
598 //put something in your storage
599 emit AddonActivated(name: name, addon: addon)
600 let networkWallet = self.networkWallet.borrow() ?? panic("The network is not up")
601 networkWallet.deposit(from: <- vault)
602 }
603
604 access(all) fun buyAddonDapper(merchAccount: Address, name:String, addon:String, vault: @DapperUtilityCoin.Vault) {
605 FIND.checkMerchantAddress(merchAccount)
606
607 if !self.leases.containsKey(name) {
608 panic("Invalid name=".concat(name))
609 }
610
611 let network=FIND.account.storage.borrow<&Network>(from: FIND.NetworkStoragePath)!
612
613 if !network.publicEnabled {
614 panic("Public registration is not enabled yet")
615 }
616
617 if network.addonPrices[addon] == nil {
618 panic("This addon is not available. addon : ".concat(addon))
619 }
620 let addonPrice = network.addonPrices[addon]!
621
622 let lease = self.borrowAuth(name)
623
624 if !lease.validate() {
625 panic("This is not a valid lease. Lease already expires and some other user registered it. Lease : ".concat(name))
626 }
627
628 if lease.addons.containsKey(addon) {
629 panic("You already have this addon : ".concat(addon))
630 }
631
632 if vault.balance != addonPrice {
633 panic("Expect ".concat(addonPrice.toString()).concat(" Dapper Credit for ").concat(addon).concat(" addon"))
634 }
635
636 lease.addAddon(addon)
637
638 //put something in your storage
639 emit AddonActivated(name: name, addon: addon)
640
641 // This is here just to check if the network is up
642 let networkWallet = self.networkWallet.borrow() ?? panic("The network is not up")
643
644 let wallet = getAccount(merchAccount).capabilities.borrow<&{FungibleToken.Receiver}>(/public/dapperUtilityCoinReceiver) ?? panic("Cannot borrow reference to Dapper Merch Account receiver. Address : ".concat(merchAccount.toString()))
645 wallet.deposit(from: <- vault)
646 }
647
648 access(account) fun adminAddAddon(name:String, addon:String) {
649 if !self.leases.containsKey(name) {
650 panic("Invalid name=".concat(name))
651 }
652
653 let network=FIND.account.storage.borrow<&Network>(from: FIND.NetworkStoragePath)!
654
655 if !network.publicEnabled {
656 panic("Public registration is not enabled yet")
657 }
658
659 if network.addonPrices[addon] == nil {
660 panic("This addon is not available. addon : ".concat(addon))
661 }
662 let addonPrice = network.addonPrices[addon]!
663
664 let lease = self.borrowAuth(name)
665
666 if !lease.validate() {
667 panic("This is not a valid lease. Lease already expires and some other user registered it. Lease : ".concat(name))
668 }
669
670 if lease.addons.containsKey(addon) {
671 panic("You already have this addon : ".concat(addon))
672 }
673
674 lease.addAddon(addon)
675
676 //put something in your storage
677 emit AddonActivated(name: name, addon: addon)
678 }
679
680 access(all) fun getAddon(name: String) : [String] {
681 let lease = self.borrowAuth(name)
682 if !lease.validate() {
683 return []
684 }
685 return lease.getAddon()
686 }
687
688 access(all) fun checkAddon(name:String, addon: String) : Bool {
689 let lease = self.borrowAuth(name)
690 if !lease.validate() {
691 return false
692 }
693 return lease.checkAddon(addon: addon)
694 }
695
696 access(all) fun getLeaseUUID(_ name: String) : UInt64 {
697 return self.borrowAuth(name).uuid
698 }
699
700 access(all) fun getLease(_ name: String) : LeaseInformation? {
701 if !self.leases.containsKey(name) {
702 return nil
703 }
704 let token=self.borrowAuth(name)
705
706 if !token.validate() {
707 return nil
708 }
709
710
711 var latestBid: UFix64? = nil
712 var auctionEnds: UFix64?= nil
713 var latestBidBy: Address?=nil
714
715 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.getAddon())
716 }
717
718 access(account) fun getNames() : [String] {
719 return self.leases.keys
720 }
721
722 access(account) fun containsName(_ name: String) : Bool {
723 return self.leases.containsKey(name)
724 }
725
726 access(all) fun getLeaseInformation() : [LeaseInformation] {
727 var info: [LeaseInformation]=[]
728 for name in self.leases.keys {
729 // if !FIND.validateFindName(name) {
730 // continue
731 // }
732 let lease=self.getLease(name)
733 if lease != nil && lease!.status != "FREE" {
734 info.append(lease!)
735 }
736 }
737 return info
738 }
739
740
741 access(LeaseOwner) fun move(name: String, profile: Capability<&{Profile.Public}>, to: Capability<&LeaseCollection>) {
742
743 let lease = self.borrowAuth(name)
744 if !lease.validate() {
745 panic("This is not a valid lease. Lease already expires and some other user registered it. Lease : ".concat(name))
746 }
747
748 let token <- self.leases.remove(key: name) ?? panic("missing NFT")
749 emit Moved(name: name, previousOwner:self.owner!.address, newOwner: profile.address, validUntil: token.getLeaseExpireTime(), lockedUntil: token.getLeaseLockedUntil())
750 token.move(profile: profile)
751 let walletRef = to.borrow() ?? panic("The receiver capability is not valid. wallet address : ".concat(to.address.toString()))
752 walletRef.deposit(token: <- token)
753
754 }
755
756 //depoit a lease token into the lease collection, not available from the outside
757 access(contract) fun deposit(token: @FIND.Lease) {
758 // add the new token to the dictionary which removes the old one
759 let oldToken <- self.leases[token.name] <- token
760
761 destroy oldToken
762 }
763
764 // getIDs returns an array of the IDs that are in the collection
765 access(all) fun getLeases(): [String] {
766 var list : [String] = []
767 for key in self.leases.keys {
768 let lease = self.borrow(key)
769 if !lease.validate() {
770 continue
771 }
772 list.append(key)
773 }
774 return list
775 }
776
777 access(all) fun getInvalidatedLeases(): [String] {
778 var list : [String] = []
779 for key in self.leases.keys {
780 let lease = self.borrow(key)
781 if lease.validate() {
782 continue
783 }
784 list.append(key)
785 }
786 return list
787 }
788
789 // borrowNFT gets a reference to an NFT in the collection
790 // so that the caller can read its metadata and call its methods
791 access(all) fun borrow(_ name: String): &FIND.Lease {
792 return (&self.leases[name])!
793 }
794
795 access(LeaseOwner) fun borrowAuth(_ name: String): auth(LeaseOwner) &FIND.Lease {
796 return (&self.leases[name])!
797 }
798
799 //borrow the auction
800 access(all) fun borrowAuction(_ name: String): &FIND.Auction {
801 return (&self.auctions[name])!
802 }
803
804
805 //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
806 access(LeaseOwner) fun register(name: String, vault: @FlowToken.Vault){
807 let profileCap = self.owner!.capabilities.get<&{Profile.Public}>(Profile.publicPath)
808 let leases= self.owner!.capabilities.get<&{FIND.LeaseCollectionPublic}>(FIND.LeasePublicPath)
809
810 let network=FIND.account.storage.borrow<&Network>(from: FIND.NetworkStoragePath)!
811
812 if !network.publicEnabled {
813 panic("Public registration is not enabled yet")
814 }
815
816 network.register(name:name, vault: <- vault, profile: profileCap, leases: leases)
817 }
818
819 //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
820 access(LeaseOwner) fun registerDapper(merchAccount: Address, name: String, vault: @DapperUtilityCoin.Vault){
821 let profileCap = self.owner!.capabilities.get<&{Profile.Public}>(Profile.publicPath)
822 let leases= self.owner!.capabilities.get<&{FIND.LeaseCollectionPublic}>(FIND.LeasePublicPath)
823
824 let network=FIND.account.storage.borrow<&Network>(from: FIND.NetworkStoragePath)!
825
826 if !network.publicEnabled {
827 panic("Public registration is not enabled yet")
828 }
829
830 network.registerDapper(merchAccount: merchAccount, name:name, vault: <- vault, profile: profileCap, leases: leases)
831 }
832
833 access(LeaseOwner) fun cleanUpInvalidatedLease(_ name: String) {
834 let lease = self.borrowAuth(name)
835 if lease.validate() {
836 panic("This is a valid lease. You cannot clean this up. Lease : ".concat(name))
837 }
838 destroy <- self.leases.remove(key: name)!
839 }
840 }
841
842 //Create an empty lease collection that store your leases to a name
843 access(all) fun createEmptyLeaseCollection(): @FIND.LeaseCollection {
844 if let network = self.account.storage.borrow<&Network>(from: FIND.NetworkStoragePath) {
845 return <- create LeaseCollection(networkCut:network.secondaryCut, networkWallet: network.wallet)
846 }
847 panic("Network is not set up")
848 }
849
850
851
852 /*
853 Core network things
854 //===================================================================================================================
855 */
856 //a struct that represents a lease of a name in the network.
857 access(all) struct NetworkLease {
858 access(all) let registeredTime: UFix64
859 access(all) var validUntil: UFix64
860 access(all) var lockedUntil: UFix64
861 access(all) var profile: Capability<&{Profile.Public}>
862 // This address is wrong for some account and can never be refered
863 access(all) var address: Address
864 access(all) var name: String
865
866 init( validUntil:UFix64, lockedUntil:UFix64, profile: Capability<&{Profile.Public}>, name: String) {
867 self.validUntil=validUntil
868 self.lockedUntil=lockedUntil
869 self.registeredTime=Clock.time()
870 self.profile=profile
871 self.address= profile.address
872 self.name=name
873 }
874
875 access(all) fun setValidUntil(_ unit: UFix64) {
876 self.validUntil=unit
877 }
878
879 access(all) fun setLockedUntil(_ unit: UFix64) {
880 self.lockedUntil=unit
881 }
882
883 access(all) fun status() : LeaseStatus {
884 let time=Clock.time()
885
886 if time >= self.lockedUntil {
887 return LeaseStatus.FREE
888 }
889
890 if time >= self.validUntil {
891 return LeaseStatus.LOCKED
892 }
893 return LeaseStatus.TAKEN
894 }
895
896 access(all) fun setProfile (_ profile: Capability<&{Profile.Public}>) {
897 self.profile=profile
898 }
899 }
900
901
902 /*
903 FREE, does not exist in profiles dictionary
904 TAKEN, registered with a time that is currentTime + leasePeriod
905 LOCKED, after TAKEN.time you will get a new status and the new time will be
906
907 */
908
909 access(all) enum LeaseStatus: UInt8 {
910 access(all) case FREE
911 access(all) case TAKEN
912 access(all) case LOCKED
913 }
914
915 /*
916 The main network resource that holds the state of the names in the network
917 */
918 access(all) resource Network {
919 access(contract) var wallet: Capability<&{FungibleToken.Receiver}>
920 access(contract) let leasePeriod: UFix64
921 access(contract) let lockPeriod: UFix64
922 access(contract) var defaultPrice: UFix64
923 access(contract) let secondaryCut: UFix64
924 access(contract) var pricesChangedAt: UFix64
925 access(contract) var lengthPrices: {Int: UFix64}
926 access(contract) var addonPrices: {String: UFix64}
927
928 access(contract) var publicEnabled: Bool
929
930 //map from name to lease for that name
931 access(contract) let profiles: { String: NetworkLease}
932
933 init(leasePeriod: UFix64, lockPeriod: UFix64, secondaryCut: UFix64, defaultPrice: UFix64, lengthPrices: {Int:UFix64}, wallet:Capability<&{FungibleToken.Receiver}>, publicEnabled:Bool) {
934 self.leasePeriod=leasePeriod
935 self.addonPrices = {
936 "forge" : 50.0 , // will have to run transactions on this when update on mainnet.
937 "premiumForge" : 1000.0
938 }
939 self.lockPeriod=lockPeriod
940 self.secondaryCut=secondaryCut
941 self.defaultPrice=defaultPrice
942 self.lengthPrices=lengthPrices
943 self.profiles={}
944 self.wallet=wallet
945 self.pricesChangedAt= Clock.time()
946 self.publicEnabled=publicEnabled
947 }
948
949 access(all) fun getLease(_ name: String) : NetworkLease? {
950 return self.profiles[name]
951 }
952
953 access(account) fun setAddonPrice(name:String, price:UFix64) {
954 self.addonPrices[name]=price
955 }
956
957 access(account) fun setPrice(defaultPrice: UFix64, additionalPrices: {Int: UFix64}) {
958 self.defaultPrice=defaultPrice
959 self.lengthPrices=additionalPrices
960 }
961
962 access(contract) fun renew(name: String, vault: @FlowToken.Vault) {
963 if let lease= self.profiles[name] {
964 let cost= FIND.calculateCostInFlow(name)
965 if vault.balance != cost {
966 panic("Vault did not contain ".concat(cost.toString()).concat(" amount of Flow"))
967 }
968 let walletRef = self.wallet.borrow() ?? panic("The receiver capability is invalid. Wallet address : ".concat(self.wallet.address.toString()))
969 walletRef.deposit(from: <- vault)
970 self.internal_renew(name: name)
971 return
972 }
973 panic("Could not find profile with name=".concat(name))
974 }
975
976 access(contract) fun renewDapper(merchAccount: Address, name: String, vault: @DapperUtilityCoin.Vault) {
977
978 FIND.checkMerchantAddress(merchAccount)
979
980 if let lease= self.profiles[name] {
981 let cost= self.calculateCost(name)
982 if vault.balance != cost {
983 panic("Vault did not contain ".concat(cost.toString()).concat(" amount of Dapper Credit"))
984 }
985 let walletRef = getAccount(merchAccount).capabilities.borrow<&{FungibleToken.Receiver}>(/public/dapperUtilityCoinReceiver) ?? panic("Cannot borrow reference to Dapper Merch Account receiver. Address : ".concat(merchAccount.toString()))
986 walletRef.deposit(from: <- vault)
987 self.internal_renew(name: name)
988 return
989 }
990 panic("Could not find profile with name=".concat(name))
991 }
992
993 access(account) fun internal_renew(name: String) {
994 if let lease= self.profiles[name] {
995
996 var newTime=0.0
997 if lease.status() == LeaseStatus.TAKEN {
998 //the name is taken but not expired so we extend the total period of the lease
999 lease.setValidUntil(lease.validUntil + self.leasePeriod)
1000 } else {
1001 lease.setValidUntil(Clock.time() + self.leasePeriod)
1002 }
1003 lease.setLockedUntil(lease.validUntil+ self.lockPeriod)
1004
1005
1006 emit Register(name: name, owner:lease.profile.address, validUntil: lease.validUntil, lockedUntil: lease.lockedUntil)
1007 self.profiles[name] = lease
1008 return
1009 }
1010 panic("Could not find profile with name=".concat(name))
1011 }
1012
1013 access(account) fun getLeaseExpireTime(_ name: String) : UFix64{
1014 if let lease= self.profiles[name] {
1015 return lease.validUntil
1016 }
1017 panic("Could not find profile with name=".concat(name))
1018 }
1019
1020 access(account) fun getLeaseLockedUntil(_ name: String) : UFix64{
1021 if let lease= self.profiles[name] {
1022 return lease.lockedUntil
1023 }
1024 panic("Could not find profile with name=".concat(name))
1025 }
1026
1027 //moving leases are done from the lease collection
1028 access(contract) fun move(name: String, profile: Capability<&{Profile.Public}>) {
1029 if let lease= self.profiles[name] {
1030 lease.setProfile(profile)
1031 self.profiles[name] = lease
1032 return
1033 }
1034 panic("Could not find profile with name=".concat(name))
1035 }
1036
1037 //everybody can call register, normally done through the convenience method in the contract
1038 access(all) fun register(name: String, vault: @FlowToken.Vault, profile: Capability<&{Profile.Public}>, leases: Capability<&{LeaseCollectionPublic}>) {
1039
1040 if name.length < 3 {
1041 panic( "A FIND name has to be minimum 3 letters long")
1042 }
1043
1044 let nameStatus=self.readStatus(name)
1045 if nameStatus.status == LeaseStatus.TAKEN {
1046 panic("Name already registered")
1047 }
1048
1049 //if we have a locked profile that is not owned by the same identity then panic
1050 if nameStatus.status == LeaseStatus.LOCKED {
1051 panic("Name is locked")
1052 }
1053
1054 let cost= FIND.calculateCostInFlow(name)
1055 if vault.balance != cost {
1056 panic("Vault did not contain ".concat(cost.toString()).concat(" amount of Flow"))
1057 }
1058 self.wallet.borrow()!.deposit(from: <- vault)
1059
1060 self.internal_register(name: name, profile: profile, leases: leases)
1061 }
1062
1063 //everybody can call register, normally done through the convenience method in the contract
1064 access(all) fun registerDapper(merchAccount: Address, name: String, vault: @DapperUtilityCoin.Vault, profile: Capability<&{Profile.Public}>, leases: Capability<&{LeaseCollectionPublic}>) {
1065 FIND.checkMerchantAddress(merchAccount)
1066
1067 if name.length < 3 {
1068 panic( "A FIND name has to be minimum 3 letters long")
1069 }
1070
1071 let nameStatus=self.readStatus(name)
1072 if nameStatus.status == LeaseStatus.TAKEN {
1073 panic("Name already registered")
1074 }
1075
1076 //if we have a locked profile that is not owned by the same identity then panic
1077 if nameStatus.status == LeaseStatus.LOCKED {
1078 panic("Name is locked")
1079 }
1080
1081 let cost= self.calculateCost(name)
1082 if vault.balance != cost {
1083 panic("Vault did not contain ".concat(cost.toString()).concat(" amount of Dapper Credit"))
1084 }
1085
1086 let walletRef = getAccount(merchAccount).capabilities.borrow<&{FungibleToken.Receiver}>(/public/dapperUtilityCoinReceiver) ?? panic("Cannot borrow reference to Dapper Merch Account receiver. Address : ".concat(merchAccount.toString()))
1087 walletRef.deposit(from: <- vault)
1088 self.internal_register(name: name, profile: profile, leases: leases)
1089 }
1090
1091 access(account) fun internal_register(name: String, profile: Capability<&{Profile.Public}>, leases: Capability<&{LeaseCollectionPublic}>) {
1092
1093 if name.length < 3 {
1094 panic("A FIND name has to be minimum 3 letters long")
1095 }
1096 if !leases.check() {
1097 panic("The lease collection capability is invalid.")
1098 }
1099 if !profile.check() {
1100 panic("The profile capability is invalid")
1101 }
1102
1103 let nameStatus=self.readStatus(name)
1104 if nameStatus.status == LeaseStatus.TAKEN {
1105 panic("Name already registered")
1106 }
1107
1108 //if we have a locked profile that is not owned by the same identity then panic
1109 if nameStatus.status == LeaseStatus.LOCKED {
1110 panic("Name is locked")
1111 }
1112
1113 let lease= NetworkLease(
1114 validUntil:Clock.time() + self.leasePeriod,
1115 lockedUntil: Clock.time() + self.leasePeriod+ self.lockPeriod,
1116 profile: profile,
1117 name: name
1118 )
1119
1120 emit Register(name: name, owner:profile.address, validUntil: lease.validUntil, lockedUntil: lease.lockedUntil)
1121 emit Name(name: name)
1122
1123 let profileRef = profile.borrow()!
1124
1125 if profileRef.getFindName() == "" {
1126 profileRef.setFindName(name)
1127 }
1128
1129 self.profiles[name] = lease
1130
1131 leases.borrow()!.deposit(token: <- create Lease(name: name, networkCap: FIND.account.capabilities.storage.issue<&Network>(FIND.NetworkStoragePath)))
1132
1133 }
1134
1135 access(all) fun readStatus(_ name: String): NameStatus {
1136 let currentTime=Clock.time()
1137 if let lease= self.profiles[name] {
1138 if !lease.profile.check() {
1139 return NameStatus(status: LeaseStatus.TAKEN, owner: nil)
1140 }
1141 let owner=lease.profile.borrow()!.owner!.address
1142 return NameStatus(status: lease.status(), owner: owner)
1143 }
1144 return NameStatus(status:LeaseStatus.FREE, owner: nil)
1145 }
1146
1147 access(account) fun profile(_ name: String) : &{Profile.Public}? {
1148 let nameStatus=self.readStatus(name)
1149 if nameStatus.status == LeaseStatus.FREE {
1150 return nil
1151 }
1152
1153 if let lease=self.profiles[name] {
1154 return lease.profile.borrow()
1155 }
1156 return nil
1157 }
1158
1159 //lookup a name that is not locked
1160 access(all) fun lookup(_ name: String) : &{Profile.Public}? {
1161 let nameStatus=self.readStatus(name)
1162 if nameStatus.status != LeaseStatus.TAKEN {
1163 return nil
1164 }
1165
1166 if let lease=self.profiles[name] {
1167 return lease.profile.borrow()
1168 }
1169 return nil
1170 }
1171
1172 access(all) fun calculateCost(_ name: String) : UFix64 {
1173 if self.lengthPrices[name.length] != nil {
1174 return self.lengthPrices[name.length]!
1175 } else {
1176 return self.defaultPrice
1177 }
1178 }
1179
1180 access(account) fun setWallet(_ wallet: Capability<&{FungibleToken.Receiver}>) {
1181 self.wallet=wallet
1182 }
1183
1184 access(account) fun setPublicEnabled(_ enabled: Bool) {
1185 self.publicEnabled=enabled
1186 }
1187
1188 access(all) fun getSecondaryCut() : UFix64 {
1189 return self.secondaryCut
1190 }
1191
1192 access(all) fun getWallet() : Capability<&{FungibleToken.Receiver}> {
1193 return self.wallet
1194 }
1195 }
1196
1197 access(all) fun getFindNetworkAddress() : Address {
1198 return self.account.address
1199 }
1200
1201
1202
1203 access(all) fun validateFindName(_ value: String) : Bool {
1204 if value.length < 3 || value.length > 16 {
1205 return false
1206 }
1207 if !FIND.validateAlphanumericLowerDash(value) {
1208 return false
1209 }
1210
1211 if value.length==16 && FIND.validateHex(value) {
1212 return false
1213 }
1214
1215 return true
1216 }
1217
1218 access(all) fun validateAlphanumericLowerDash(_ value:String) : Bool {
1219 let lowerA: UInt8=97
1220 let lowerZ: UInt8=122
1221
1222 let dash:UInt8=45
1223 let number0:UInt8=48
1224 let number9:UInt8=57
1225
1226 let bytes=value.utf8
1227 for byte in bytes {
1228 if byte >= lowerA && byte <= lowerZ {
1229 continue
1230 }
1231 if byte >= number0 && byte <= number9 {
1232 continue
1233 }
1234
1235 if byte == dash {
1236 continue
1237 }
1238 return false
1239
1240 }
1241 return true
1242
1243 }
1244
1245 access(all) fun validateHex(_ value:String) : Bool {
1246 let lowerA: UInt8=97
1247 let lowerF: UInt8=102
1248
1249 let number0:UInt8=48
1250 let number9:UInt8=57
1251
1252 let bytes=value.utf8
1253 for byte in bytes {
1254 if byte >= lowerA && byte <= lowerF {
1255 continue
1256 }
1257 if byte >= number0 && byte <= number9 {
1258 continue
1259 }
1260 return false
1261
1262 }
1263 return true
1264
1265 }
1266
1267 access(all) fun trimFindSuffix(_ name: String) : String {
1268 return FindUtils.trimSuffix(name, suffix: ".find")
1269 }
1270
1271 access(contract) fun checkMerchantAddress(_ merchAccount: Address) {
1272 // If only find can sign the trxns and call this function, then we do not have to check the address passed in.
1273 // Otherwise, would it be wiser if we hard code the address here?
1274
1275 if FIND.account.address == 0x097bafa4e0b48eef {
1276 // This is for mainnet
1277 if merchAccount != 0x55459409d30274ee {
1278 panic("Merch Account address does not match with expected")
1279 }
1280 } else if FIND.account.address == 0x35717efbbce11c74 {
1281 // This is for testnet
1282 if merchAccount != 0x4748780c8bf65e19{
1283 panic("Merch Account address does not match with expected")
1284 }
1285 } else {
1286 // otherwise falls into emulator and user dapper
1287 if merchAccount != 0x179b6b1cb6755e31 {
1288 panic("Merch Account address does not match with expected ".concat(merchAccount.toString()))
1289 }
1290 }
1291 }
1292
1293
1294 access(account) fun getFlowUSDOracleAddress() : Address {
1295 // If only find can sign the trxns and call this function, then we do not have to check the address passed in.
1296 // Otherwise, would it be wiser if we hard code the address here?
1297 if FIND.account.address == 0x097bafa4e0b48eef {
1298 // This is for mainnet
1299 return 0xe385412159992e11
1300 } else if FIND.account.address == 0x35717efbbce11c74 {
1301 // This is for testnet
1302 return 0xcbdb5a7b89c3c844
1303 } else {
1304 //otherwise on emulator we use same account as FIND
1305 return self.account.address
1306 }
1307 }
1308
1309 access(account) fun getMerchantAddress() : Address {
1310 // If only find can sign the trxns and call this function, then we do not have to check the address passed in.
1311 // Otherwise, would it be wiser if we hard code the address here?
1312
1313 if FIND.account.address == 0x097bafa4e0b48eef {
1314 // This is for mainnet
1315 return 0x55459409d30274ee
1316 } else if FIND.account.address == 0x35717efbbce11c74 {
1317 // This is for testnet
1318 return 0x4748780c8bf65e19
1319 } else {
1320 // otherwise falls into emulator and user dapper
1321 return 0x179b6b1cb6755e31
1322 }
1323 }
1324
1325 init() {
1326 self.NetworkStoragePath= /storage/FIND
1327
1328 self.LeasePublicPath=/public/findLeases
1329 self.LeaseStoragePath=/storage/findLeases
1330
1331 // Get
1332 let wallet=self.account.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
1333
1334 // these values are hardcoded here for a reason. Then plan is to throw away the key and not have setters for them so that people can trust the contract to be the same
1335 let network <- create Network(
1336 leasePeriod: 31536000.0, //365 days
1337 lockPeriod: 7776000.0, //90 days
1338 secondaryCut: 0.05,
1339 defaultPrice: 5.0,
1340 lengthPrices: {3: 500.0, 4:100.0},
1341 wallet: wallet,
1342 publicEnabled: false
1343 )
1344 self.account.storage.save(<-network, to: FIND.NetworkStoragePath)
1345 }
1346
1347 //These are deprecated resources that we cannot have remove
1348 access(all) struct BidInfo{}
1349 access(all) resource Auction { }
1350 access(all) resource Bid { }
1351 access(all) resource interface BidCollectionPublic { }
1352 access(all) resource BidCollection: BidCollectionPublic { }
1353
1354}
1355