Smart Contract
aiSportsEscrow
A.4fdb077419808080.aiSportsEscrow
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}