Smart Contract

FastBreakEscrow

A.254b32edc33e5bc3.FastBreakEscrow

Valid From

131,995,636

Deployed

6d ago
Feb 21, 2026, 04:31:05 PM UTC

Dependents

15 imports
1import aiSportsEscrow from 0x4fdb077419808080
2import FlowToken from 0x1654653399040a61
3import FastBreakV1 from 0x0b2a3299cc857e29
4import FungibleToken from 0xf233dcee88fe0abe
5
6access(all) contract FastBreakEscrow {
7
8  access(all) let EscrowAdminStoragePath: StoragePath
9
10  /// A game of Fast Break has the following status transitions
11  access(all) enum EscrowStatus: UInt8 {
12      access(all) case OPEN /// Game is open for submission
13      access(all) case CLOSED /// Game is over and rewards are being distributed
14  }
15
16  access (all) let FBEscrowEntryById: {UInt64 : FastBreakEscrow.FBEscrowEntry}
17  access (all) let UserFBEscrowsByAddress: {Address: [UInt64]}
18  access (contract) var nextFBEscrowEntryId: UInt64
19  access (all) let FBEscrowContestById: {UInt64 : FastBreakEscrow.FBEscrowContest}
20
21  access (all) struct FBEscrowContest {
22    access(all) let escrowID: UInt64
23    access(all) let fastBreakGameId: String 
24    access(all) var totalEntries: UInt64
25    access(all) let FBEscrowEntries: [UInt64]
26    access(all) let protocolFees: UFix64
27    access(all) let dues: UFix64
28
29    access(contract) fun addFundingEntries(numEntries: UInt64) {
30      self.totalEntries = self.totalEntries + numEntries
31    }
32
33    access(contract) fun addNewEntry(entryId: UInt64, numEntries: UInt64) {
34      self.totalEntries = self.totalEntries + numEntries
35      self.FBEscrowEntries.append(entryId)
36    }
37
38    init(escrowID: UInt64, fastBreakGameId: String, protocolFees: UFix64, dues: UFix64) {
39      self.escrowID = escrowID
40      self.fastBreakGameId = fastBreakGameId
41      self.totalEntries = 0
42      self.FBEscrowEntries = []
43      self.protocolFees = protocolFees
44      self.dues = dues
45    }
46  }
47
48  //represents an entry to the FBEscrow
49  access(all) struct FBEscrowEntry {
50    access(all) let escrowID: UInt64
51    access(all) var points: UInt64
52    access(all) var win: Bool
53    access(all) var winnings: UFix64
54    access(all) var submittedAt: UInt64
55    access(all) let fastBreakPlayerId: UInt64
56    access(all) var status: FastBreakEscrow.EscrowStatus
57    access(all) let flowAddress: Address
58    access(all) let fbAddress: Address
59    access(all) let numEntries: UInt32
60    access(all) let duesPaid: UFix64
61    access(all) let FBEscrowEntryId: UInt64
62
63    init(escrowID: UInt64, fastBreakPlayerId: UInt64, flowAddress: Address, fbAddress: Address, numEntries: UInt32, duesPaid: UFix64, id: UInt64) {
64      self.escrowID = escrowID
65      self.points = 0
66      self.win = false
67      self.winnings = 0.0
68      self.submittedAt = UInt64(getCurrentBlock().timestamp)
69      self.status = FastBreakEscrow.EscrowStatus.OPEN
70      self.fastBreakPlayerId = fastBreakPlayerId
71      self.flowAddress = flowAddress
72      self.fbAddress = fbAddress
73      self.numEntries = numEntries
74      self.duesPaid = duesPaid
75      self.FBEscrowEntryId = id
76    }    
77
78    access(contract) fun setResults(points: UInt64, win: Bool) {
79      self.points = points
80      self.win = win
81      self.status = FastBreakEscrow.EscrowStatus.CLOSED
82    }
83
84    access(contract) fun setWinnings(winnings: UFix64) {
85      self.winnings = winnings
86    }
87
88    access(contract) fun setClosed(points: UInt64) {
89      self.status = FastBreakEscrow.EscrowStatus.CLOSED
90      self.points = points
91    }
92  }
93
94  access (all) fun getUserFBEscrows(userAddress: Address) : [FastBreakEscrow.FBEscrowEntry] {
95    let userEscrowEntries = FastBreakEscrow.UserFBEscrowsByAddress[userAddress] ?? []
96    let userFBEscrows: [FastBreakEscrow.FBEscrowEntry] = []
97
98    for entryId in userEscrowEntries {
99      let entry = FastBreakEscrow.FBEscrowEntryById[entryId] ?? panic("Entry not found")
100      userFBEscrows.append(entry)
101    }
102    return userFBEscrows
103  }
104
105  access(all) fun fundFBEscrow(escrowAddress: Address, escrowID: UInt64, flowTokenVault: @{FungibleToken.Vault}){
106
107    let amount: UFix64 = flowTokenVault.balance
108    assert(UFix64(UInt64(amount)) == amount, message: "Amount must be a whole number")
109
110    let amountUInt64: UInt64 = UInt64(amount)
111
112    let escrowAccount = getAccount(escrowAddress)
113    let escrowRef = escrowAccount.capabilities.borrow<&aiSportsEscrow.EscrowMinter>(aiSportsEscrow.EscrowMinterPublicPath)
114                      ?? panic("Resource does not exist.")
115    escrowRef.addFunds(escrowID: escrowID, tokens: <-flowTokenVault)
116
117    let contestsRef: &{UInt64 : FastBreakEscrow.FBEscrowContest} = &FastBreakEscrow.FBEscrowContestById
118    let fastBreakEscrowContestRef = contestsRef[escrowID] ?? panic("No escrow found")
119    fastBreakEscrowContestRef.addFundingEntries(numEntries: amountUInt64)
120  }
121
122
123  access(all) fun joinFBEscrow(escrowAddress: Address, escrowID: UInt64, entries: Int, flowTokenVault: @{FungibleToken.Vault}, signerAddress: Address, fbAddress: Address){
124
125    let escrowAccount = getAccount(escrowAddress) //Hardcode emulator account address - CHANGE TO GLOBAL
126    let escrowRef = escrowAccount.capabilities.borrow<&aiSportsEscrow.Escrow>(aiSportsEscrow.getEscrowPublicPath(id: escrowID)) ?? panic("Could not borrow escrow reference")
127    let leagueDues = escrowRef.leagueDues * UFix64(entries)
128
129    assert(flowTokenVault.balance >= leagueDues, message: "Not enough tokens to join escrow.")
130
131    let minterRef = escrowAccount.capabilities.borrow<&aiSportsEscrow.EscrowMinter>(aiSportsEscrow.EscrowMinterPublicPath)
132      ?? panic("Resource does not exist.")
133
134    minterRef.joinEscrow(escrowID: escrowID, tokens: <- flowTokenVault, sender: signerAddress, nfts: <-[], entries: entries)
135
136    let entryId = FastBreakEscrow.nextFBEscrowEntryId
137    FastBreakEscrow.nextFBEscrowEntryId = entryId + 1
138
139    var fastBreakPlayerId = FastBreakV1.getPlayerIdByAccount(accountAddress: fbAddress)
140
141    let contestsRef: &{UInt64 : FastBreakEscrow.FBEscrowContest} = &FastBreakEscrow.FBEscrowContestById
142    let fastBreakEscrowContestRef = contestsRef[escrowID] ?? panic("No escrow found")
143    fastBreakEscrowContestRef.addNewEntry(entryId: entryId, numEntries: UInt64(entries))
144
145    //get the users submissioin to the FB contest
146    let gameDict = FastBreakV1.getFastBreakGame(id: fastBreakEscrowContestRef.fastBreakGameId) ?? panic("Game does not exist")
147    assert(gameDict.getFastBreakSubmissionByPlayerId(playerId: fastBreakPlayerId) != nil, message: "No submission found for player")
148
149    let fastBreakGameId = fastBreakEscrowContestRef.fastBreakGameId
150    
151    let userEntry = FastBreakEscrow.FBEscrowEntry(escrowID: escrowID, fastBreakPlayerId: fastBreakPlayerId, flowAddress: signerAddress, fbAddress: fbAddress, numEntries: UInt32(entries), duesPaid: leagueDues, id: entryId)
152    
153    //get users Escrow entries
154    let userEscrowEntries = FastBreakEscrow.UserFBEscrowsByAddress[signerAddress] ?? []
155
156    userEscrowEntries.append(entryId)
157
158    FastBreakEscrow.UserFBEscrowsByAddress.insert(key: signerAddress, userEscrowEntries)  
159
160    FastBreakEscrow.FBEscrowEntryById.insert(key: entryId, userEntry)
161
162  }
163
164  access(all) resource FBEscrowAdmin {
165    
166    access(all) fun createFBEscrow(escrowID: UInt64, fastBreakGameId: String, protocolFees: UFix64, dues: UFix64) {
167      assert(FastBreakV1.getFastBreakGame(id: fastBreakGameId) != nil, message: "Game does not exist")
168      let newFBEscrowEntry = FastBreakEscrow.FBEscrowContest(escrowID: escrowID, fastBreakGameId: fastBreakGameId, protocolFees: protocolFees, dues: dues)
169      FastBreakEscrow.FBEscrowContestById.insert(key: escrowID, newFBEscrowEntry)
170    }
171
172    access (all) fun closeFBEscrow(escrowAddress: Address, escrowID: UInt64): {Address: UFix64} {
173      let escrow = FastBreakEscrow.FBEscrowContestById[escrowID] ?? panic("No escrow found")
174      let escrowEntries = escrow.FBEscrowEntries
175
176      let escrowAccount = getAccount(escrowAddress) //Hardcode emulator account address - CHANGE TO GLOBAL
177      let escrowRef = escrowAccount.capabilities.borrow<&aiSportsEscrow.Escrow>(aiSportsEscrow.getEscrowPublicPath(id: escrowID)) ?? panic("Could not borrow escrow reference")
178
179      let winners: [UInt64] = []
180      var totalWinningEntries: UInt32 = 0
181
182      let gameDict = FastBreakV1.getFastBreakGame(id: escrow.fastBreakGameId) ?? panic("Game does not exist")
183
184      for entryId in escrowEntries {
185        let entriesRef: &{UInt64 : FastBreakEscrow.FBEscrowEntry} = &FastBreakEscrow.FBEscrowEntryById
186        let entry = entriesRef[entryId] ?? panic("Entry not found")
187        
188        //get the users submissioin to the FB contest
189        let submission = gameDict.getFastBreakSubmissionByPlayerId(playerId: entry.fastBreakPlayerId) ?? panic("No submission found for player")
190
191        if submission.win == true {
192          winners.append(entryId)
193          totalWinningEntries = totalWinningEntries + entry.numEntries
194          entry.setResults(points: submission.points, win: submission.win)
195        } else {
196          entry.setClosed(points: submission.points)
197        }
198      }
199
200      let totalDistro = escrowRef.tokens.balance - (escrowRef.tokens.balance * escrow.protocolFees/100.0)
201      let winnerDistro: {Address: UFix64} = {}
202
203      for winner in winners {
204        let entriesRef: &{UInt64:FastBreakEscrow.FBEscrowEntry} = &FastBreakEscrow.FBEscrowEntryById
205        let entry = entriesRef[winner] ?? panic("Entry not found")
206
207        let winnings = totalDistro * (UFix64(entry.numEntries) / UFix64(totalWinningEntries))
208        entry.setWinnings(winnings: winnings)
209
210        winnerDistro[entry.flowAddress] = winnings
211      }
212
213      return winnerDistro
214    }
215
216  }
217
218  init(){
219    self.FBEscrowEntryById = {}
220    self.UserFBEscrowsByAddress = {}
221    self.FBEscrowContestById = {}
222    self.nextFBEscrowEntryId = 1
223
224        // Set the named paths
225    self.EscrowAdminStoragePath = /storage/FBEscrowAdmin
226
227    self.account.storage.save(<-create FBEscrowAdmin(), to: self.EscrowAdminStoragePath)
228  }
229
230}