Smart Contract

JoyrideAccounts

A.ecfad18ba9582d4f.JoyrideAccounts

Deployed

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

Dependents

0 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import JoyrideMultiToken from 0xecfad18ba9582d4f
3pub contract JoyrideAccounts {
4
5    access(contract) var gameToDeveloperAccount : {String: Address}
6    access(contract) var playerIDToPlayer : {String: Address}
7    access(contract) var treasuryCapability : Capability<&JoyrideMultiToken.PlatformAdmin>?
8    access(contract) var playerAccounts : @{String: PlayerAccount}
9    access(contract) var playerIdByAddress : {Address: String}
10
11    pub event AccountCreated(playerID: String, referralID: String?, accountAddress: Address)
12    pub event LinkDeveloperAccount(accountAddress: Address, gameID: String)
13    pub event AccountAlreadyExists(playerID: String, accountAddress: Address)
14    pub event InsufficientBalance(playerID: String, txID: String, amount: UFix64, currentBalance: UFix64)
15    init() {
16        self.playerIDToPlayer = {}
17        self.gameToDeveloperAccount = {}
18        self.playerAccounts <- {}
19        self.treasuryCapability = nil
20        self.playerIdByAddress = {}
21
22        self.account.save(<- create JoyrideAccountsAdmin(), to: /storage/JoyrideAccountsAdmin)
23        let cap = self.account.link<&JoyrideAccountsAdmin{GrantsTokenRewards, SharesProfits, PlayerAccounts}>(/private/JoyrideAccountsAdmin, target: /storage/JoyrideAccountsAdmin)
24    }
25
26    //Pretty sure this is safe to be public, since a valid Capability<&{JRXToken.Treasury}> can only be created by the JRXToken contract account.
27    pub fun linkTreasuryCapability(treasuryCapability: Capability<&JoyrideMultiToken.PlatformAdmin>) {
28        if(!treasuryCapability.check()) {panic("Capability from Invalid Source")}
29        self.treasuryCapability = treasuryCapability
30    }
31
32    pub fun PlayerRegisterCapability(capability:Capability<&JoyrideMultiToken.Vault>) {
33        let playerID = JoyrideAccounts.playerIdByAddress[capability.address] ?? panic("PlayerID not Found by Address")
34        let playerAccount <- JoyrideAccounts.playerAccounts.remove(key: playerID)  ?? panic("PlayerID not found in JoyrideAccounts")
35        playerAccount.RegisterVaultCapability(capability: capability)
36        destroy JoyrideAccounts.playerAccounts.insert(key: playerID, <-playerAccount)
37	}
38
39    pub fun getPlayerAccount(playerID: String) : PublicAccount {
40        if(self.playerIDToPlayer[playerID] == nil) { panic("Player not found for playerID: ".concat(playerID)) }
41        return getAccount(self.playerIDToPlayer[playerID]!)
42    }
43
44    pub resource interface ReceivesPlayerRewards {
45        access(contract) fun ReceiveRewards(totalRewards: UFix64, rewardID: String, tokenContext: String) : Bool
46    }
47
48    pub resource PlayerAccount : ReceivesPlayerRewards {
49        pub let playerID: String
50        pub var accountAddress: Address?
51        access(self) var vaultCapability: Capability<&JoyrideMultiToken.Vault>?
52
53        init(playerID: String, referralID: String?) {
54            self.playerID = playerID
55            self.accountAddress = nil
56            self.vaultCapability = nil
57        }
58
59        access(contract) fun ReceiveRewards(totalRewards: UFix64, rewardID: String, tokenContext: String) : Bool {
60            if(self.vaultCapability == nil || JoyrideAccounts.treasuryCapability == nil) { return false }
61
62            let playerVault = self.vaultCapability!.borrow()
63            let treasury = JoyrideAccounts.treasuryCapability!.borrow()
64            if(playerVault == nil || treasury == nil) { return false }
65
66            let rewardVault <- treasury!.withdraw(vault: JoyrideMultiToken.Vaults.treasury, tokenContext: tokenContext, amount: totalRewards, purpose: rewardID)
67            if(rewardVault == nil) {
68                destroy rewardVault
69                return false
70            }
71            else {
72                playerVault!.depositToken(from: <-rewardVault!)
73                return true
74            }
75        }
76
77        //This must be called if the resource is ever transfered to another account.
78        access(contract) fun AssociatePaymentAddress(address: Address) {
79            JoyrideAccounts.playerIDToPlayer[self.playerID] = address
80            self.accountAddress = address
81            JoyrideAccounts.playerIdByAddress[address] = self.playerID
82        }
83
84        access(contract) fun WithdrawTokensForPayment(playerID: String, txID: String, amount: UFix64, tokenContext: String): @FungibleToken.Vault? {
85            if(self.vaultCapability == nil) {
86                return nil
87            }
88
89            let vault = self.vaultCapability!.borrow()
90            if(vault == nil) {
91                return nil
92            }
93
94            let currentBalance: UFix64 = vault?.balanceOf(tokenContext: tokenContext) ?? 0.0;
95            if(currentBalance < amount){
96                emit InsufficientBalance(playerID:playerID, txID:txID, amount:amount, currentBalance:currentBalance)
97                return nil;
98            }
99
100            let withdrawal <- vault?.withdrawToken(tokenContext: tokenContext, amount: amount)
101            if(withdrawal?.balance != amount) {
102                destroy <- withdrawal!
103                return nil
104            } else {
105                return <- withdrawal
106            }
107        }
108
109        access(contract) fun DepositTokens(vault: @FungibleToken.Vault) {
110            let playerVault = (self.vaultCapability ?? panic("No Capability Registered")).borrow() ?? panic("Capability could not be borrowed!")
111            playerVault.depositToken(from: <- vault)
112        }
113
114        access(contract) fun RegisterVaultCapability(capability:Capability<&JoyrideMultiToken.Vault>) {
115            if(capability.address != self.accountAddress) {panic("Cannot register capability to incorrect address")}
116            self.vaultCapability = capability
117        }
118    }
119
120    //Rewards and Payments Interfaces
121    //
122    //
123    //
124
125    pub resource interface GrantsTokenRewards {
126        pub fun GrantTokenRewards(playerID: String, amount: UFix64, tokenContext: String, rewardID: String) : Bool
127    }
128
129    pub resource interface SharesProfits {
130        pub fun ShareProfits(profits:@FungibleToken.Vault, inGameID: String, fromPlayerID: String) : @FungibleToken.Vault
131        pub fun ShareProfitsWithDevPercentage(profits:@FungibleToken.Vault, inGameID: String, fromPlayerID: String, devPercentage: UFix64) : @FungibleToken.Vault
132    }
133
134    pub resource interface PlayerAccounts {
135        pub fun GetPlayerJRXAccount(playerID: String) : &PlayerAccount?
136        pub fun AddPlayerAccount(playerID: String, referralID: String?, account: PublicAccount)
137        pub fun EscrowWithdraw(playerID: String, amount:UFix64, tokenContext: String): @FungibleToken.Vault?
138        pub fun PlayerDeposit(playerID: String, vault:@FungibleToken.Vault) : @FungibleToken.Vault?
139        pub fun AddDeveloperAccount(address: Address, gameID: String)
140        pub fun TransferPlayerAccount(playerID: String, to: PublicAccount)
141        pub fun EscrowWithdrawWithTnxId(playerID: String, txID: String, amount:UFix64, tokenContext: String): @FungibleToken.Vault?
142    }
143
144    pub resource JoyrideAccountsAdmin : GrantsTokenRewards, SharesProfits, PlayerAccounts {
145        pub fun GrantTokenRewards(playerID: String, amount: UFix64, tokenContext: String, rewardID: String) : Bool {
146            let playerAccount <- JoyrideAccounts.playerAccounts.remove(key: playerID)
147            if(playerAccount == nil) {
148                destroy playerAccount
149                return false
150            } else {
151                playerAccount?.ReceiveRewards(totalRewards: amount, rewardID: rewardID, tokenContext: tokenContext)
152                destroy JoyrideAccounts.playerAccounts.insert(key: playerID, <-playerAccount!)
153                return true
154            }
155        }
156
157        pub fun AddPlayerAccount(playerID: String, referralID: String?, account: PublicAccount) {
158            var checkAccount <- JoyrideAccounts.playerAccounts.remove(key: playerID)
159            if(checkAccount == nil){
160                destroy checkAccount
161                var playerAccount <-create PlayerAccount(playerID: playerID, referralID: referralID)
162                playerAccount.AssociatePaymentAddress(address: account.address)
163                destroy JoyrideAccounts.playerAccounts.insert(key: playerID, <-playerAccount)
164                emit AccountCreated(playerID: playerID, referralID: referralID, accountAddress: account.address)
165            }else {
166                checkAccount?.AssociatePaymentAddress(address: account.address)
167                destroy JoyrideAccounts.playerAccounts.insert(key: playerID, <-checkAccount!)
168                emit AccountAlreadyExists(playerID: playerID, accountAddress: account.address)
169            }
170        }
171
172        pub fun AddDeveloperAccount(address: Address, gameID: String) {
173            JoyrideAccounts.gameToDeveloperAccount[gameID] = address
174            emit LinkDeveloperAccount(accountAddress: address, gameID: gameID)
175        }
176
177        pub fun TransferPlayerAccount(playerID: String, to: PublicAccount) {
178            let checkAccount <- JoyrideAccounts.playerAccounts.remove(key: playerID) ?? panic("PlayerID not found!")
179            checkAccount.AssociatePaymentAddress(address: to.address)
180            destroy JoyrideAccounts.playerAccounts.insert(key: playerID, <-checkAccount)
181        }
182
183        pub fun GetPlayerJRXAccount(playerID: String) : &PlayerAccount? {
184            if(JoyrideAccounts.playerAccounts.containsKey(playerID)) {
185                let playerAccount <- JoyrideAccounts.playerAccounts.remove(key: playerID)!
186                let reference = &playerAccount as  &PlayerAccount
187                destroy JoyrideAccounts.playerAccounts.insert(key: playerID, <- playerAccount)
188                return reference
189            }
190            return nil
191        }
192
193        pub fun ShareProfits(profits:@FungibleToken.Vault, inGameID: String, fromPlayerID: String) : @FungibleToken.Vault {
194            return <- self.ShareProfitsWithDevPercentage(profits: <- profits, inGameID: inGameID, fromPlayerID: fromPlayerID, devPercentage: 0.0)
195        }
196
197        pub fun ShareProfitsWithDevPercentage(profits:@FungibleToken.Vault, inGameID: String, fromPlayerID: String, devPercentage: UFix64) : @FungibleToken.Vault {
198            let playerAddress = JoyrideAccounts.playerIDToPlayer[fromPlayerID]
199            let playerAccount = self.GetPlayerJRXAccount(playerID:fromPlayerID)
200
201            let developerStaking = JoyrideAccounts.gameToDeveloperAccount[inGameID]
202            if(developerStaking == nil) {
203                return <- profits
204            }
205            else {
206                let vaultCapability = getAccount(developerStaking!).getCapability<&{JoyrideMultiToken.Receiver}>(JoyrideMultiToken.UserPublicPath).borrow()
207                if(vaultCapability == nil) {
208                    return <- profits
209                }
210                else {
211                    let devShare <- profits.withdraw(amount: profits.balance * devPercentage)
212                    vaultCapability!.depositToken(from: <- devShare)
213                    return <- profits
214                }
215            }
216        }
217
218        pub fun EscrowWithdraw(playerID: String, amount:UFix64, tokenContext: String): @FungibleToken.Vault? {
219            let playerAccount = self.GetPlayerJRXAccount(playerID:playerID)
220            if(playerAccount == nil) {
221                return nil
222            }
223            return <- playerAccount!.WithdrawTokensForPayment(playerID: playerID, txID:"", amount:amount, tokenContext: tokenContext)
224        }
225
226        pub fun EscrowWithdrawWithTnxId(playerID: String, txID: String, amount:UFix64, tokenContext: String): @FungibleToken.Vault? {
227          let playerAccount = self.GetPlayerJRXAccount(playerID:playerID)
228          if(playerAccount == nil) {
229             return nil
230          }
231          return <- playerAccount!.WithdrawTokensForPayment(playerID: playerID, txID:txID, amount:amount, tokenContext: tokenContext)
232       }
233
234        pub fun PlayerDeposit(playerID: String, vault:@FungibleToken.Vault) : @FungibleToken.Vault? {
235            let playerAccount = self.GetPlayerJRXAccount(playerID:playerID)
236            if(playerAccount != nil) {
237                playerAccount!.DepositTokens(vault: <-vault)
238                return nil
239            } else {
240                return <- vault
241            }
242        }
243    }
244
245    pub struct CreateAccountTransactionData {
246        pub var playerID: String
247        pub var referralID: String
248
249        init(playerID: String, referralID: String) {
250            self.playerID = playerID
251            self.referralID = referralID
252        }
253    }
254}