Smart Contract

aiSportsEscrow

A.4fdb077419808080.aiSportsEscrow

Valid From

138,133,962

Deployed

1w ago
Feb 15, 2026, 08:50:09 PM UTC

Dependents

49 imports
1import FlowToken from 0x1654653399040a61
2import FungibleToken from 0xf233dcee88fe0abe
3import NonFungibleToken from 0x1d7e57aa55817448
4
5access(all) contract aiSportsEscrow {
6
7  access(all) entitlement Withdraw
8  access(all) entitlement LeagueOwner
9  access(all) entitlement EmptyEscrow
10
11  access (all) event EscrowCreated(escrowID: UInt64)
12  access (all) event EscrowJoined(escrowID: UInt64)
13  /// Emitted when a user successfully joins a multi-entry contest.
14  /// `numEntries` is the number of entries purchased in this call.
15  access (all) event MultiEntryJoined(userAddress: Address, contestId: UInt64, numEntries: Int)
16  access (all) event EscrowClosed(escrowID: UInt64)
17  access (all) event EscrowFunded(escrowID: UInt64)
18
19  //list of open escrows
20  access(all) var openEscrows: {UInt64: Bool}
21
22  // ID tracker for escrows
23  access(all) var nextEscrowID: UInt64
24
25  // Standard Paths
26  access(all) let EscrowMinterStoragePath: StoragePath
27  access(all) let EscrowMinterPublicPath: PublicPath
28  access(all) let EscrowStoragePath: StoragePath
29
30  access(all) resource Escrow {
31    //fungible tokens held in escrow
32    access(all) let tokens: @{FungibleToken.Vault} 
33    //Time when the escrow can be released
34    access(all) let releaseTime: UFix64
35    //time when the contest start - people cannot join the escrow after the start time
36    access(all) let startTime: UFix64
37    access(all) let escrowID: UInt64
38    access(all) let leagueName: String
39    access(all) var isEscrowOpen: Bool
40    
41    access (all) let leagueHost: String
42    //Yahoo/ESPN LeagueID - optional
43    access(all) let leagueID: UInt64 
44    //Maximum amount of players that can join
45    access(all) let totalMembers: UInt32
46    //How many tokens each member has to pay for dues
47    access(all) let leagueDues: UFix64
48    //league creator is always 1st member - leagueMembers[0]
49    access(all) var leagueMembers: [Address]
50
51    //dictionary of NFTs held in Escrow
52    access(all) var nftEscrow: @{Address: [{NonFungibleToken.NFT}]}
53
54    //Required number of NFTs to escrow to join league
55    access(all) let requiredNFTs: UInt64
56    //Path To NFT Collection
57    access(all) let nftCollectionPath: PublicPath
58
59    //getNftType
60    access(all) let nftType: Type
61
62    access(all) fun getTokenType(): Type {
63        return self.tokens.getType()
64    }
65
66    // Initialize the Escrow resource with tokens and the release time
67    init(tokens: @{FungibleToken.Vault}, startTime: UFix64?, releaseTime: UFix64, escrowID: UInt64, leagueHost: String?, leagueName: String, leagueID: UInt64?, totalMembers: UInt32, leagueDues: UFix64, creatorAddress: Address?, nfts: @[{NonFungibleToken.NFT}], path: PublicPath?) {
68      
69        self.tokens <- tokens
70        self.isEscrowOpen = true
71        self.startTime = startTime != nil ? startTime! : 0.0
72        self.releaseTime = releaseTime
73        self.escrowID = escrowID
74        self.leagueName = leagueName
75        self.leagueHost = leagueHost != nil ? leagueHost! : ""
76        self.leagueID = leagueID != nil ? leagueID! : 0
77        self.totalMembers = totalMembers
78        self.leagueDues = leagueDues
79        self.leagueMembers = creatorAddress != nil ? [creatorAddress!] : []
80        self.requiredNFTs = nfts.length > 0 ? UInt64(nfts.length) : 0
81        self.nftCollectionPath = path != nil ? path! : PublicPath(identifier: "")!
82
83        if nfts.length > 0 {
84          self.nftType = nfts[0].getType()
85
86          var i = 1
87          while i < nfts.length {
88            assert( nfts[i].getType() == self.nftType, message: "All NFTs must be of the same type")
89            i = i + 1
90          }
91
92        } else {
93          //nft type is not needed, so we just set it to a generic NFT
94          self.nftType = Type<@{NonFungibleToken.NFT}>()
95        } 
96
97        //if there is a creator && the league accepts NFTS, then creator will escrow their NFTs
98        if(creatorAddress != nil) {
99          let createAddress = creatorAddress!
100          self.nftEscrow <- {createAddress: <-nfts}
101        } else { //if there is not a creator, 0 NFTs are escrowed, and the nft resource that is passed in is destroyed
102          assert(nfts.length == 0, message: "NFTs passed in, but no NFT requirement for this escrow")
103          self.nftEscrow <- {}
104          destroy nfts
105        }
106
107    }
108
109    // Entitlement for withdrawal
110    access(Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
111        pre {
112            // Ensure that the current time is past the release time
113            getCurrentBlock().timestamp >= self.releaseTime: "Escrow period has not yet ended"
114            Int(self.tokens.balance) >= Int(amount): "Insufficient balance"
115        }
116
117        // Return the held Flow tokens
118        return <-self.tokens.withdraw(amount: amount)
119    }
120
121    access(Withdraw) fun refund(): @{FungibleToken.Vault} {
122
123        pre {
124            // Ensure that the start time is null or has not yet occured
125            getCurrentBlock().timestamp < self.startTime || self.startTime == 0.0: "Cannot refund, Escrow has already started"
126            Int(self.tokens.balance) >= Int(self.leagueDues): "Insufficient balance"
127        }
128
129        // Return the held Flow tokens
130        return <-self.tokens.withdraw(amount: self.leagueDues)
131    }
132
133    access(contract) fun addMember(address: Address, nfts: @[{NonFungibleToken.NFT}]) {
134      
135      pre {
136        // Ensure that the address is not already a member
137        self.leagueMembers.contains(address) == false: "Address is already a member"
138        Int(self.requiredNFTs) == nfts.length: "Incorrect number of NFTs"
139      }
140
141      self.leagueMembers.append(address)
142
143      if nfts.length > 0 { //this is a league with an nft requirement
144
145        var i = 0
146        while i < nfts.length {
147          assert( nfts[i].getType() == self.nftType, message: "All NFTs in Escrow must be of the same type")
148          i = i + 1
149        }
150        self.nftEscrow[address] <-! nfts
151      } else { //this league does not require nfts, and this array contained 0 resources
152        destroy nfts
153      }
154
155    }
156
157    /// Adds `numEntries` entries for `address` by appending `address` to `leagueMembers` `numEntries` times.
158    ///
159    /// This is only intended for multi-entry contests that do not accept NFTs.
160    access(contract) fun addMultipleMembers(address: Address, numEntries: Int) {
161      pre {
162        numEntries >= 1: "numEntries must be at least 1"
163        self.requiredNFTs == 0: "Multi-entry contests do not support NFT escrow"
164      }
165
166      var i = 0
167      while i < numEntries {
168        self.leagueMembers.append(address)
169        i = i + 1
170      }
171    }
172
173    access(contract) fun releaseEscrowedNfts(address: Address){
174
175      let nfts: @[{NonFungibleToken.NFT}] <- self.nftEscrow.remove(key: address) ?? panic("Address does not exist in the dictionary")
176      let receiver = getAccount(address)
177      let receiverCap = receiver.capabilities.get<&{NonFungibleToken.CollectionPublic}>(self.nftCollectionPath)
178      let receiverRef = receiverCap.borrow() ?? panic("Could not borrow reference to the recipient's receiver")
179
180      while nfts.length > 0 {
181        let nft <- nfts.remove(at: 0)
182        receiverRef.deposit(token: <-nft)
183      }
184
185      assert(nfts.length == 0, message: "NFTs were not transferred to account")
186      //destroy empty nft array
187      destroy nfts
188    }
189
190    access (contract) fun closeEscrowStatus() {
191      self.isEscrowOpen = false
192    }
193
194  }
195
196  access(all) view fun getEscrowStoragePath(id: UInt64): StoragePath {
197    return StoragePath(identifier: "escrows/".concat(id.toString()))!
198  }
199
200  access(all) view fun getEscrowPublicPath(id: UInt64): PublicPath {
201    return PublicPath(identifier: "escrows/".concat(id.toString()))!
202  }
203
204  /// createEmptyCollection creates an empty Closer Collection
205  /// and returns it to the caller so that they can store EscrowClosers
206  access(all) fun createCloserCollection(): @EscrowCloserCollection {    
207      return <- create EscrowCloserCollection()
208  }
209
210  /// Minter
211  ///
212  /// Resource object that token admin accounts can hold to create new Escrows
213  ///
214  access(all) resource EscrowMinter {
215    // Create a new escrow
216    access(all) fun createEscrow(tokens: @{FungibleToken.Vault}, startTime: UFix64?, releaseTime: UFix64, leagueHost: String?, leagueName: String, leagueID: UInt64?, totalMembers:UInt32, dues: UFix64, creator: Address, nfts: @[{NonFungibleToken.NFT}], path: PublicPath?): @EscrowCloser {
217
218      pre {
219        dues >= 1.0: "Dues Must be at least 1 Token"
220        totalMembers >= 2: "League must have at least 2 members"
221        getCurrentBlock().timestamp < releaseTime: "Release time must be in the future"
222        tokens.balance >= dues: "Insufficient dues"
223        startTime == nil || startTime! <= releaseTime: "Start time must be before release time"
224        startTime == nil || getCurrentBlock().timestamp < startTime!: "Start time must be in the future"
225      }
226
227      // Store it in the contract
228      let escrowID = aiSportsEscrow.nextEscrowID
229      // Create the escrow resource
230      let escrow <- create Escrow(tokens: <-tokens, startTime: startTime, releaseTime: releaseTime, escrowID: escrowID, leagueHost: leagueHost, leagueName: leagueName, leagueID: leagueID, totalMembers: totalMembers, leagueDues: dues, creatorAddress: creator, nfts: <-nfts, path: path)
231
232      let escrowStoragePath = aiSportsEscrow.getEscrowStoragePath(id: escrowID)
233      let escrowPublicPath = aiSportsEscrow.getEscrowPublicPath(id: escrowID)
234
235      aiSportsEscrow.account.storage.save(<-escrow, to: escrowStoragePath)
236
237      //publish a capability to the escrow in storage so others can join
238      let escrowCap = aiSportsEscrow.account.capabilities.storage.issue<&aiSportsEscrow.Escrow>(escrowStoragePath)
239      aiSportsEscrow.account.capabilities.publish(escrowCap, at: escrowPublicPath)
240      //create an escrowCloser to save to creators account
241      let escrowCloser <- create aiSportsEscrow.EscrowCloser(escrowID: escrowID)
242      //add this escrow to open escrows
243      aiSportsEscrow.openEscrows.insert(key: escrowID, true)
244      // Increment the escrow ID counter
245      aiSportsEscrow.nextEscrowID = escrowID + 1
246      //emit escrow created event
247      emit EscrowCreated(escrowID: escrowID)
248
249      return <- escrowCloser
250
251    }
252
253    access(all) fun joinEscrow(escrowID: UInt64, tokens: @{FungibleToken.Vault}, sender: Address, nfts: @[{NonFungibleToken.NFT}], entries: Int?) {
254
255      let joinEscrowRef = aiSportsEscrow.account.storage.borrow<&aiSportsEscrow.Escrow>(from: aiSportsEscrow.getEscrowStoragePath(id: escrowID)) ?? panic("Could not borrow a reference to the escrow resource")
256      let updatedLeagueMembers = *joinEscrowRef.leagueMembers
257
258      let userEntries = entries ?? 1
259
260      if(joinEscrowRef.startTime > 0.0) { //if start time is 0.0, there is not start time requirement
261        assert(getCurrentBlock().timestamp < joinEscrowRef.startTime, message: "Cannot Join Escrow after start time")
262      }
263
264      assert(getCurrentBlock().timestamp < joinEscrowRef.releaseTime, message: "Cannot Join Escrow after release time")
265      assert(joinEscrowRef.tokens.getType() == tokens.getType(), message: "Incorrect Token Type")
266      assert(updatedLeagueMembers.length < Int(joinEscrowRef.totalMembers), message: "League At Capacity")
267      assert((joinEscrowRef.leagueDues * UFix64(userEntries)) == tokens.balance, message: "Incorrect Dues")
268
269      joinEscrowRef.tokens.deposit(from: <-tokens)
270      joinEscrowRef.addMember(address: sender, nfts: <-nfts)
271
272      //emit escrow joined event
273      emit EscrowJoined(escrowID: escrowID)
274    }
275
276    /// Join a multi-entry contest by purchasing `numEntries` entries in a single atomic call.
277    ///
278    /// Backwards compatibility:
279    /// - `joinEscrow` continues to enforce one entry per address (legacy behavior).
280    /// - Multi-entry behavior is only exposed via this function.
281    ///
282    /// Notes:
283    /// - Multi-entry contests are restricted to league names starting with `paid_juice`.
284    /// - Multi-entry contests do not accept NFTs (`requiredNFTs` must be 0).
285    access(all) fun joinMultiEntryEscrow(
286      escrowID: UInt64,
287      tokens: @{FungibleToken.Vault},
288      sender: Address,
289      numEntries: Int
290    ) {
291      let maxEntriesPerUser = 100
292      let leaguePrefix = "paid_juice"
293
294      assert(numEntries >= 1, message: "numEntries must be at least 1")
295      assert(numEntries <= maxEntriesPerUser, message: "numEntries exceeds max entries per user")
296
297      let joinEscrowRef = aiSportsEscrow.account.storage.borrow<&aiSportsEscrow.Escrow>(
298        from: aiSportsEscrow.getEscrowStoragePath(id: escrowID)
299      ) ?? panic("Could not borrow a reference to the escrow resource")
300
301      // Restrict multi-entry contests to league names starting with `paid_juice*`
302      if joinEscrowRef.leagueName.length < leaguePrefix.length {
303        panic("Multi-entry is not enabled for this contest")
304      }
305
306      let actualPrefix = joinEscrowRef.leagueName.slice(from: 0, upTo: leaguePrefix.length)
307      assert(actualPrefix == leaguePrefix, message: "Multi-entry is not enabled for this contest")
308      assert(joinEscrowRef.requiredNFTs == 0, message: "Multi-entry contests do not support NFTs")
309
310      if joinEscrowRef.startTime > 0.0 {
311        assert(getCurrentBlock().timestamp < joinEscrowRef.startTime, message: "Cannot Join Escrow after start time")
312      }
313
314      assert(getCurrentBlock().timestamp < joinEscrowRef.releaseTime, message: "Cannot Join Escrow after release time")
315      assert(joinEscrowRef.tokens.getType() == tokens.getType(), message: "Incorrect Token Type")
316
317      // Enforce global contest capacity in terms of total entries.
318      let currentEntries = joinEscrowRef.leagueMembers.length
319      assert(currentEntries + numEntries <= Int(joinEscrowRef.totalMembers), message: "League At Capacity")
320
321      // Enforce per-user max entries by counting the sender's current entries.
322      var existingEntriesForUser = 0
323      for member in joinEscrowRef.leagueMembers {
324        if member == sender {
325          existingEntriesForUser = existingEntriesForUser + 1
326        }
327      }
328      assert(existingEntriesForUser + numEntries <= maxEntriesPerUser, message: "Max entries per user exceeded")
329
330      // Ensure caller paid exactly dues * numEntries
331      assert((joinEscrowRef.leagueDues * UFix64(numEntries)) == tokens.balance, message: "Incorrect Dues")
332
333      joinEscrowRef.tokens.deposit(from: <-tokens)
334      joinEscrowRef.addMultipleMembers(address: sender, numEntries: numEntries)
335
336      // Emit both for backwards compatibility (legacy listeners) + richer multi-entry indexing.
337      emit EscrowJoined(escrowID: escrowID)
338      emit MultiEntryJoined(userAddress: sender, contestId: escrowID, numEntries: numEntries)
339    }
340
341    access(all) fun addFunds(escrowID: UInt64, tokens: @{FungibleToken.Vault}) { 
342      let joinEscrowRef = aiSportsEscrow.account.storage.borrow<&aiSportsEscrow.Escrow>(from: aiSportsEscrow.getEscrowStoragePath(id: escrowID)) ?? panic("Could not borrow a reference to the escrow resource")
343      assert(joinEscrowRef.tokens.getType() == tokens.getType(), message: "Incorrect Token Type")
344
345      let addFundsRef = aiSportsEscrow.account.storage.borrow<&aiSportsEscrow.Escrow>(from: aiSportsEscrow.getEscrowStoragePath(id: escrowID)) ?? panic("Could not borrow a reference to the escrow resource")
346      addFundsRef.tokens.deposit(from: <-tokens)
347      emit EscrowFunded(escrowID: escrowID)
348    }
349
350    access (EmptyEscrow) fun createEmptyEscrow(tokens: @{FungibleToken.Vault}, startTime: UFix64?, releaseTime: UFix64, leagueName: String, totalMembers:UInt32, dues: UFix64 ): @EscrowCloser {
351      
352      pre {
353        totalMembers >= 1: "League must have at least 1 members"
354        getCurrentBlock().timestamp < releaseTime: "Release time must be in the future"
355        startTime == nil || startTime! <= releaseTime: "Start time must be before release time"
356        startTime == nil || getCurrentBlock().timestamp < startTime!: "Start time must be in the future"
357      }
358
359      // Store it in the contract
360      let escrowID = aiSportsEscrow.nextEscrowID
361      // Create the escrow resource
362      let escrow <- create Escrow(tokens: <-tokens, startTime: startTime, releaseTime: releaseTime, escrowID: escrowID, leagueHost: nil, leagueName: leagueName, leagueID: nil, totalMembers: totalMembers, leagueDues: dues, creatorAddress: nil , nfts: <-[], path: nil)
363
364      let escrowStoragePath = aiSportsEscrow.getEscrowStoragePath(id: escrowID)
365      let escrowPublicPath = aiSportsEscrow.getEscrowPublicPath(id: escrowID)
366
367      aiSportsEscrow.account.storage.save(<-escrow, to: escrowStoragePath)
368
369      //publish a capability to the escrow in storage so others can join
370      let escrowCap = aiSportsEscrow.account.capabilities.storage.issue<&aiSportsEscrow.Escrow>(escrowStoragePath)
371      aiSportsEscrow.account.capabilities.publish(escrowCap, at: escrowPublicPath)
372      //create an escrowCloser to save to creators account
373      let escrowCloser <- create aiSportsEscrow.EscrowCloser(escrowID: escrowID)
374      //add this escrow to open escrows
375      aiSportsEscrow.openEscrows.insert(key: escrowID, true)
376      // Increment the escrow ID counter
377      aiSportsEscrow.nextEscrowID = escrowID + 1
378      //emit escrow created event
379      emit EscrowCreated(escrowID: escrowID)
380
381      return <- escrowCloser
382    }
383
384  }
385
386  access(all) resource EscrowCloserCollection {
387    
388    access(all) var escrows: @{UInt64: aiSportsEscrow.EscrowCloser}
389
390    access(all) fun deposit(escrowCloser: @EscrowCloser) {
391      self.escrows[escrowCloser.escrowID] <-! escrowCloser
392    }
393
394    access(LeagueOwner) fun closeEscrow(escrowID: UInt64, winners: {Address: UFix64}) {
395      self.escrows[escrowID]?.releaseEscrow(winners: winners, path: nil, overflow: nil) ?? panic("Could not access EscrowID")
396    }
397
398    access(LeagueOwner) fun closeEscrowGeneric(escrowID: UInt64, winners: {Address: UFix64}, path: PublicPath?, overflow: Address) {
399      self.escrows[escrowID]?.releaseEscrow(winners: winners, path: path, overflow: overflow) ?? panic("Could not access EscrowID")
400    }
401
402    access(LeagueOwner) fun cancelEscrow(escrowID: UInt64) {
403      self.escrows[escrowID]?.refundEscrow() ?? panic("Could not access EscrowID")
404    }
405
406    access(all) fun getEscrowIDs(): [UInt64] {
407      return self.escrows.keys
408    }
409
410    init() {
411      self.escrows <- {}
412    }
413  }
414
415  access(all) resource EscrowCloser {
416    
417    access (all) let escrowID: UInt64
418
419    //function to make sure the winners array has only unique elements
420    access (all) fun hasUniqueElements(winners: [Address]): Bool {
421
422      let readWinners:[Address] = []
423
424      for index, element in winners {
425          if readWinners.contains(element) {
426            return false
427          } else {
428            readWinners.append(element)
429          }
430      }
431
432      return true
433    }
434    
435    // Release the escrow by ID
436    access(all) fun releaseEscrow(winners: {Address: UFix64}, path: PublicPath?, overflow: Address?) {
437
438      pre {
439        aiSportsEscrow.openEscrows.containsKey(self.escrowID): "Escrow is not open"
440      }
441
442      var tokenPath = PublicPath(identifier: "flowTokenReceiver")!
443
444      if path != nil {
445        tokenPath = path!
446      }
447
448      assert (self.hasUniqueElements(winners: winners.keys), message: "Winners must be unique")
449
450      let releaseEscrowRef = aiSportsEscrow.account.storage.borrow<auth(Withdraw) &aiSportsEscrow.Escrow>(from: aiSportsEscrow.getEscrowStoragePath(id: self.escrowID)) ?? panic("Could not borrow a reference to the escrow resource")
451
452      assert (getCurrentBlock().timestamp >= releaseEscrowRef.releaseTime, message: "Escrow period has not yet ended")
453
454      var totalPayout = 0.0
455
456      for element in winners.values {
457        totalPayout = totalPayout + element
458      }
459
460      if (overflow == nil) {
461        assert(totalPayout == releaseEscrowRef.tokens.balance, message: "Pay out does not match escrow balance")
462      }
463
464      //pay out each winners FLOW tokens
465      for element in winners.keys {
466
467        assert(releaseEscrowRef.leagueMembers.contains(element), message: "Winner is not a member of the league")
468
469        let winnerAccount = getAccount(element)
470        let payout <- releaseEscrowRef.withdraw(amount: winners[element] ?? panic("Could not find winner in winners dictionary"))
471
472        // get the Winner Account's Receiver reference to their Vault
473        // by borrowing the reference from the public capability
474        let receiverRef = winnerAccount.capabilities.borrow<&{FungibleToken.Receiver}>(tokenPath)
475                          ?? panic("Could not borrow a reference to the receiver")
476
477        // deposit tokens to their Vault
478        receiverRef.deposit(from: <-payout)
479      }
480
481      if(overflow != nil && releaseEscrowRef.tokens.balance > 0.0) { // if there is a token balance, payout the rest of the escrow to the league creator
482        let creatorAccount = getAccount(overflow!)
483        let payout <- releaseEscrowRef.withdraw(amount: releaseEscrowRef.tokens.balance)
484
485        // get the Winner Account's Receiver reference to their Vault
486        // by borrowing the reference from the public capability
487        let receiverRef = creatorAccount.capabilities.borrow<&{FungibleToken.Receiver}>(tokenPath)
488                          ?? panic("Could not borrow a reference to the receiver")
489
490        // deposit the rest of the tokens to their Vault
491        receiverRef.deposit(from: <-payout)
492      }
493    
494      //return all user NFTs
495      if releaseEscrowRef.requiredNFTs > 0 {
496        for member in releaseEscrowRef.leagueMembers {
497          releaseEscrowRef.releaseEscrowedNfts(address: member)
498        }
499      }
500
501      //change escrow status to closed
502      releaseEscrowRef.closeEscrowStatus()
503
504      //remove the escrow from the openEscrows dictionary
505      aiSportsEscrow.openEscrows.remove(key: releaseEscrowRef.escrowID)
506
507    }
508
509    //cancel the league and refund winners before league start
510    access (all) fun refundEscrow(){
511
512      pre {
513        aiSportsEscrow.openEscrows.containsKey(self.escrowID): "Escrow is not open"
514      }
515
516      let releaseEscrowRef = aiSportsEscrow.account.storage.borrow<auth(Withdraw) &aiSportsEscrow.Escrow>(from: aiSportsEscrow.getEscrowStoragePath(id: self.escrowID)) ?? panic("Could not borrow a reference to the escrow resource")
517      
518      assert(getCurrentBlock().timestamp < releaseEscrowRef.startTime || releaseEscrowRef.startTime == 0.0, message: "League has already started")
519
520      for member in releaseEscrowRef.leagueMembers {
521        let returnDues <- releaseEscrowRef.refund()
522        let returnAccount = getAccount(member)
523
524        let receiverRef = returnAccount.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
525                  ?? panic("Could not borrow a reference to the receiver")
526
527        // deposit your tokens to their Vault
528        receiverRef.deposit(from: <-returnDues)
529
530        //return all user NFTs
531        if releaseEscrowRef.requiredNFTs > 0 {
532          releaseEscrowRef.releaseEscrowedNfts(address: member)
533        }
534      }
535
536      //change escrow status to closed
537      releaseEscrowRef.closeEscrowStatus()
538
539      //remove the escrow from the openEscrows dictionary
540      aiSportsEscrow.openEscrows.remove(key: releaseEscrowRef.escrowID)
541
542    }
543
544    init(escrowID: UInt64){
545      self.escrowID = escrowID
546    }
547
548  }
549
550  init(){
551    self.nextEscrowID = 0
552    self.openEscrows = {}
553
554    // Set the named paths
555    self.EscrowMinterStoragePath = /storage/aiSportsEscrowMinter
556    self.EscrowMinterPublicPath = /public/aiSportsEscrowMinter
557
558    self.EscrowStoragePath = /storage/escrows
559
560    self.account.storage.save(<-create EscrowMinter(), to: self.EscrowMinterStoragePath)
561    
562    //create a public capability to the EscrowMinter resource
563    let escrowMinterCap = self.account.capabilities.storage.issue<&aiSportsEscrow.EscrowMinter>(self.EscrowMinterStoragePath)
564    self.account.capabilities.publish(escrowMinterCap, at: self.EscrowMinterPublicPath)
565  }
566}