Smart Contract
JoyridePayments
A.ecfad18ba9582d4f.JoyridePayments
1import FungibleToken from 0xf233dcee88fe0abe
2import JoyrideMultiToken from 0xecfad18ba9582d4f
3import JoyrideAccounts from 0xecfad18ba9582d4f
4import GameEscrowMarker from 0xecfad18ba9582d4f
5
6pub contract JoyridePayments {
7
8 access(contract) var accountsCapability : Capability<&{JoyrideAccounts.GrantsTokenRewards, JoyrideAccounts.SharesProfits, JoyrideAccounts.PlayerAccounts}>?
9 access(contract) var treasuryCapability : Capability<&JoyrideMultiToken.PlatformAdmin>?
10 access(contract) let authorizedBackendAccounts: {Address:Bool}
11 access(contract) let transactionStore: {String:{String:Bool}}
12
13 pub event DebitTransactionCreate(playerID: String, txID: String, tokenContext: String, amount: UFix64, gameID: String, notes: String)
14 pub event CreditTransactionCreate(playerID: String, txID: String, tokenContext: String, amount: UFix64, gameID: String, notes: String)
15 pub event DebitTransactionReverted(playerID: String, txID: String, tokenContext: String, amount: UFix64, gameID: String)
16 pub event DebitTransactionFinalized(playerID: String, txID: String, tokenContext: String, amount: UFix64, gameID: String, profit: UFix64)
17
18 pub event TxFailed_DuplicateTxID(txID: String, notes: String)
19 pub event TxFailed_ByTxTypeAndTxID(txID: String, txType: String, notes: String)
20
21 init() {
22 self.accountsCapability = nil
23 self.treasuryCapability = nil
24 self.authorizedBackendAccounts = {}
25 self.transactionStore = {}
26
27 let p2eAdmin <- create PaymentsAdmin()
28 self.account.save(<-p2eAdmin, to: /storage/PaymentsAdmin)
29 self.account.link<&PaymentsAdmin{WalletAdmin}>(/private/PaymentsAdmin, target: /storage/PaymentsAdmin)
30 }
31
32 //Pretty sure this is safe to be public, since a valid Capability<&JRXToken.AdminVault.{JRXToken.ConvertsRewards}> can only be created by the JRXToken contract account.
33 pub fun linkAccountsCapability(accountsCapability : Capability<&JoyrideAccounts.JoyrideAccountsAdmin{JoyrideAccounts.GrantsTokenRewards, JoyrideAccounts.SharesProfits, JoyrideAccounts.PlayerAccounts}>) {
34 if(!accountsCapability.check()) {panic("Capability from Invalid Source")}
35 self.accountsCapability = accountsCapability
36 }
37
38 //Pretty sure this is safe to be public, since a valid Capability<&JRXToken.AdminVault.{JRXToken.ConvertsRewards}> can only be created by the JRXToken contract account.
39 pub fun linkTreasuryCapability(treasuryCapability : Capability<&JoyrideMultiToken.PlatformAdmin>) {
40 if(!treasuryCapability.check()) {panic("Capability from Invalid Source")}
41 self.treasuryCapability = treasuryCapability
42 }
43
44 pub fun getPlay2EarnCapabilities(account: AuthAccount) : Capability<&PaymentsAdmin{WalletAdmin}> {
45 if(account.address == self.account.address || self.authorizedBackendAccounts.containsKey(account.address)){
46 return self.account.getCapability<&PaymentsAdmin{WalletAdmin}>(/private/PaymentsAdmin)
47 }
48 panic("Not Authorized")
49 }
50
51 pub resource interface WalletAdmin {
52 pub fun PlayerTransaction(playerID: String, tokenContext: String, amount:Fix64, gameID: String, txID: String, reward: Bool, notes: String) : Bool
53 pub fun FinalizeTransactionWithDevPercentage(txID: String, profit: UFix64, devPercentage: UFix64) : Bool
54 pub fun RefundTransaction(txID: String) : Bool
55 }
56
57 pub resource Transaction {
58 pub var vault : @FungibleToken.Vault
59 pub let playerID : String
60 pub let transactionID : String
61 pub let creationTime: UFix64
62 pub let gameID: String
63
64 init(vault: @FungibleToken.Vault, playerID : String, gameID : String, txID : String) {
65 let gameEscrowMarker <- GameEscrowMarker.createEmptyVault()
66 gameEscrowMarker.depositToEscrowVault(gameID: gameID, vault: <-vault)
67 self.vault <- gameEscrowMarker
68 self.playerID = playerID
69 self.transactionID = txID
70 self.gameID = gameID
71 self.creationTime = getCurrentBlock().timestamp
72 }
73
74 destroy() {
75 destroy self.vault
76 }
77
78 pub fun Refund(txID: String) : Bool {
79 if(JoyridePayments.accountsCapability == nil) {
80 emit TxFailed_ByTxTypeAndTxID(txID: txID, txType: "RefundTX", notes: txID.concat(": JoyrideAccountsAdmin Accounts Capability Null"))
81 return false
82 }
83
84 let accountsAdmin = JoyridePayments.accountsCapability!.borrow()
85 if(accountsAdmin == nil) {
86 emit TxFailed_ByTxTypeAndTxID(txID: txID, txType: "RefundTX", notes: txID.concat(": JoyrideAccountsAdmin Accounts Capability Null"))
87 return false
88 }
89 if(self.vault.isInstance(Type<@GameEscrowMarker.Vault>())) {
90 let balance = self.vault.balance
91 let escrow: @GameEscrowMarker.Vault <- self.vault.withdraw(amount: balance) as! @GameEscrowMarker.Vault
92 var vault <- escrow.withdrawFromEscrowVault(amount: balance)
93 let identifier = vault.getType().identifier
94 destroy escrow
95 return self.doRefund(vault: <- vault, balance: balance, identifier: identifier, txID: txID)
96 } else {
97 let balance = self.vault.balance
98 var vault <- self.vault.withdraw(amount: self.vault.balance)
99 let identifier = vault.getType().identifier
100 return self.doRefund(vault: <- vault, balance: balance, identifier: identifier, txID: txID)
101 }
102 }
103
104 priv fun doRefund(vault: @FungibleToken.Vault, balance: UFix64, identifier: String, txID: String): Bool {
105 let accountsAdmin = JoyridePayments.accountsCapability!.borrow()
106 let undepositable <- accountsAdmin!.PlayerDeposit(playerID:self.playerID, vault: <-vault)
107 if(undepositable != nil) {
108 emit TxFailed_ByTxTypeAndTxID(txID: txID, txType: "RefundTX", notes: txID.concat(": Failed to deposit Admin Withdrawn vault in PlayerAccount"))
109 self.vault.deposit(from: <- undepositable!)
110 return false
111 } else {
112 destroy undepositable
113 emit DebitTransactionReverted(playerID: self.playerID, txID: self.transactionID, tokenContext: identifier, amount: balance, gameID: self.gameID)
114 return true
115 }
116 }
117
118 pub fun Finalize(txID: String, profit: UFix64, devPercentage: UFix64): Bool {
119 if(JoyridePayments.accountsCapability == nil) {
120 emit TxFailed_ByTxTypeAndTxID(txID: txID, txType: "FinalizeTX", notes: txID.concat(": JoyrideAccountsAdmin Accounts Capability Null"))
121 return false
122 }
123
124 let accountsAdmin = JoyridePayments.accountsCapability!.borrow()
125 if(accountsAdmin == nil) {
126 emit TxFailed_ByTxTypeAndTxID(txID: txID, txType: "FinalizeTX", notes: txID.concat(": JoyrideAccountsAdmin Accounts Capability Null"))
127 return false
128 }
129
130 if(JoyridePayments.treasuryCapability == nil) {
131 emit TxFailed_ByTxTypeAndTxID(txID: txID, txType: "FinalizeTX", notes: txID.concat(": JoyrideMultiToken PlatformAdmin Capability Null"))
132 return false
133 }
134
135 let treasury = JoyridePayments.treasuryCapability!.borrow()
136 if(treasury == nil) {
137 emit TxFailed_ByTxTypeAndTxID(txID: txID, txType: "FinalizeTX", notes: txID.concat(": JoyrideMultiToken PlatformAdmin Capability Null"))
138 return false
139 }
140
141 if(self.vault.isInstance(Type<@GameEscrowMarker.Vault>())) {
142 let balance = self.vault.balance
143 let escrow: @GameEscrowMarker.Vault <- self.vault.withdraw(amount: balance) as! @GameEscrowMarker.Vault
144 var vault <- escrow.withdrawFromEscrowVault(amount: balance)
145 let identifier = vault.getType().identifier
146 destroy escrow
147 return self.doFinalize(vault: <- vault, balance: balance, identifier: identifier, profit: profit,txID: txID, devPercentage: devPercentage)
148 } else {
149 let balance = self.vault.balance
150 var vault <- self.vault.withdraw(amount: self.vault.balance)
151 let identifier = vault.getType().identifier
152 return self.doFinalize(vault: <- vault, balance: balance, identifier: identifier, profit: profit, txID: txID, devPercentage: devPercentage)
153 }
154 }
155
156 priv fun doFinalize(vault: @FungibleToken.Vault, balance: UFix64, identifier: String, profit: UFix64, txID: String, devPercentage: UFix64): Bool {
157 let accountsAdmin = JoyridePayments.accountsCapability!.borrow()
158 let profitVault <- vault.withdraw(amount: profit)
159 let remainder <- accountsAdmin!.ShareProfitsWithDevPercentage(profits: <- profitVault, inGameID: self.gameID, fromPlayerID: self.playerID, devPercentage: devPercentage)
160 vault.deposit(from: <- remainder)
161
162 let treasury = JoyridePayments.treasuryCapability!.borrow()
163 treasury!.deposit(vault: JoyrideMultiToken.Vaults.treasury, from: <-vault)
164 emit DebitTransactionFinalized(playerID: self.playerID, txID: self.transactionID, tokenContext: identifier, amount: balance, gameID: self.gameID, profit: profit)
165 return true
166 }
167 }
168
169 pub resource PaymentsAdmin : WalletAdmin {
170 access(self) let pendingTransactions : @{String: Transaction}
171
172 init() {
173 self.pendingTransactions <- {}
174 }
175
176 destroy() {
177 destroy self.pendingTransactions
178 }
179
180 pub fun AuthorizeBackendAccount(authorizedAddress: Address) {
181 JoyridePayments.authorizedBackendAccounts[authorizedAddress] = true
182 }
183
184 pub fun DeAuthorizeBackendAccount(deauthorizedAddress: Address) {
185 JoyridePayments.authorizedBackendAccounts.remove(key: deauthorizedAddress)
186 }
187
188 pub fun PlayerTransaction(playerID: String, tokenContext: String, amount: Fix64, gameID: String, txID: String, reward: Bool, notes: String) : Bool {
189 if(self.pendingTransactions.containsKey(txID) || (JoyridePayments.transactionStore.containsKey(playerID) && JoyridePayments.transactionStore[playerID]![txID] != nil)) {
190 emit TxFailed_DuplicateTxID(txID:txID,notes:notes)
191 return false
192 }
193
194 if(amount < 0.0) {
195 let debit = UFix64(amount * -1.0)
196 let accountManager = JoyridePayments.accountsCapability!.borrow()
197 if(accountManager == nil) {
198 emit TxFailed_ByTxTypeAndTxID(txID: txID, txType: "DebitTX", notes: txID.concat(": JoyrideAccountsAdmin Accounts Capability Null"))
199 return false
200 }
201
202 let vault <- accountManager!.EscrowWithdrawWithTnxId(playerID:playerID, txID: txID, amount:debit, tokenContext: tokenContext)
203 if(vault == nil) {
204 emit TxFailed_ByTxTypeAndTxID(txID: txID, txType: "DebitTX", notes: txID.concat(": player withdraw vault failed"))
205 destroy vault
206 return false
207 }
208
209 let tx <- create Transaction(vault: <- vault!, playerID: playerID, gameID: gameID, txID: txID)
210 destroy <- self.pendingTransactions.insert(key: txID, <- tx)
211 emit DebitTransactionCreate(playerID: playerID, txID: txID, tokenContext: tokenContext, amount: debit, gameID: gameID, notes: notes)
212 } else {
213 let credit = UFix64(amount)
214 let treasury = JoyridePayments.treasuryCapability!.borrow()
215 if(treasury == nil) {
216 emit TxFailed_ByTxTypeAndTxID(txID: txID, txType: "CreditTX", notes: txID.concat(": JoyrideMultiToken PlatformAdmin Capability Null"))
217 return false
218 }
219 let vault <- treasury!.withdraw(vault: JoyrideMultiToken.Vaults.treasury, tokenContext: tokenContext, amount: credit, purpose: notes)
220 if(vault == nil) {
221 emit TxFailed_ByTxTypeAndTxID(txID: txID, txType: "CreditTX", notes: txID.concat(": Withdraw token vault from JoyrideMultiToken PlatformAdmin account is failed"))
222 destroy vault
223 return false
224 } else {
225 let undepositable <- JoyridePayments.accountsCapability!.borrow()!.PlayerDeposit(playerID: playerID, vault: <- vault!)
226 if(undepositable != nil) {
227 emit TxFailed_ByTxTypeAndTxID(txID: txID, txType: "CreditTX", notes: txID.concat(": Failed to deposit Admin Withdrawn vault in PlayerAccount"))
228 treasury!.deposit(vault: JoyrideMultiToken.Vaults.treasury, from: <-undepositable!)
229 return false
230 } else {
231 emit CreditTransactionCreate(playerID: playerID, txID: txID, tokenContext: tokenContext, amount: credit, gameID: gameID, notes: "Vault Not Null")
232 destroy undepositable
233 }
234 }
235 }
236
237 if(!JoyridePayments.transactionStore.containsKey(playerID)) {
238 JoyridePayments.transactionStore[playerID] = {}
239 }
240
241 JoyridePayments.transactionStore[playerID]!.insert(key: txID, true)
242
243 return true
244 }
245
246 pub fun FinalizeTransaction(txID: String, profit: UFix64): Bool {
247 return self.FinalizeTransactionWithDevPercentage(txID: txID, profit: profit, devPercentage: 0.0)
248 }
249
250 pub fun FinalizeTransactionWithDevPercentage(txID: String, profit: UFix64, devPercentage: UFix64) : Bool {
251 let tx <- self.pendingTransactions.remove(key: txID)
252 if(tx == nil) {
253 emit TxFailed_ByTxTypeAndTxID(txID: txID, txType: "FinalizeTX", notes: txID.concat(": Not found in pending debit transaction list"))
254 destroy tx
255 return false
256 } else {
257 let isFinalizeSuccess = tx?.Finalize(txID: txID, profit: profit, devPercentage: devPercentage)
258 if(isFinalizeSuccess == true) {
259 destroy tx
260 return true
261 } else {
262 destroy <- self.pendingTransactions.insert(key: txID, <- tx!)
263 return false
264 }
265 }
266 }
267
268 pub fun RefundTransaction(txID: String) : Bool {
269 let tx <- self.pendingTransactions.remove(key: txID)
270 if(tx == nil) {
271 emit TxFailed_ByTxTypeAndTxID(txID: txID, txType: "RefundTX", notes: txID.concat(": Not found in pending debit transaction list"))
272 destroy tx
273 return false
274 } else {
275 let isRefundSuccess = tx?.Refund(txID: txID) // if this false then send vault back to pending transactino
276 if (isRefundSuccess == true) {
277 destroy tx
278 return true
279 } else {
280 destroy <- self.pendingTransactions.insert(key: txID, <- tx!)
281 return false
282 }
283 }
284 }
285 }
286
287 pub struct PlayerTransactionData {
288 pub var playerID: String
289 pub var reward: Fix64
290 pub var txID: String
291 pub var rewardTokens: Bool
292 pub var gameID: String
293 pub var notes: String
294 pub var tokenTransactionType: String
295 pub var profit: UFix64
296 pub var currencyTokenContext: String
297
298 init(playerID: String, reward: Fix64, txID: String, rewardTokens: Bool, gameID: String, notes: String,
299 tokenTransactionType: String, profit: UFix64, currencyTokenContext: String) {
300 self.playerID = playerID
301 self.reward = reward
302 self.txID = txID
303 self.rewardTokens = rewardTokens
304 self.gameID = gameID
305 self.notes = notes
306 self.tokenTransactionType = tokenTransactionType
307 self.profit = profit
308 self.currencyTokenContext = currencyTokenContext
309 }
310 }
311
312 pub struct PlayerTransactionDataWithDevPercentage {
313 pub var playerID: String
314 pub var reward: Fix64
315 pub var txID: String
316 pub var rewardTokens: Bool
317 pub var gameID: String
318 pub var notes: String
319 pub var tokenTransactionType: String
320 pub var profit: UFix64
321 pub var currencyTokenContext: String
322 pub var devPercentage: UFix64
323
324 init(playerID: String, reward: Fix64, txID: String, rewardTokens: Bool, gameID: String, notes: String,
325 tokenTransactionType: String, profit: UFix64, currencyTokenContext: String, devPercentage: UFix64) {
326 self.playerID = playerID
327 self.reward = reward
328 self.txID = txID
329 self.rewardTokens = rewardTokens
330 self.gameID = gameID
331 self.notes = notes
332 self.tokenTransactionType = tokenTransactionType
333 self.profit = profit
334 self.currencyTokenContext = currencyTokenContext
335 self.devPercentage = devPercentage
336 }
337 }
338
339 pub struct FinalizeTransactionData {
340 pub var txID: String
341 pub var profit: UFix64
342
343 init(txID: String, profit: UFix64) {
344 self.txID = txID
345 self.profit = profit
346 }
347 }
348
349 pub struct RevertTransactionData {
350 pub var txID: String
351
352 init(txID: String) {
353 self.txID = txID
354 }
355 }
356}