Smart Contract

JoyridePayments

A.ecfad18ba9582d4f.JoyridePayments

Deployed

1w ago
Mar 04, 2026, 06:54:51 PM UTC

Dependents

0 imports
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}