Smart Contract

DriverzStakingXx

A.f887ece39166906e.DriverzStakingXx

Deployed

1d ago
Feb 26, 2026, 09:44:38 PM UTC

Dependents

0 imports
1import NonFungibleToken from 0x1d7e57aa55817448
2import VroomToken from 0xf887ece39166906e
3import FungibleToken from 0xf233dcee88fe0abe
4import CarClub from 0xf887ece39166906e
5import Helmet from 0xf887ece39166906e
6import Wheel from 0xf887ece39166906e
7import Car from 0xf887ece39166906e
8import DriverzNFT from 0xa039bd7d55a96c0c
9
10
11access(all) contract DriverzStakingXx {
12
13    access(all) let StakingStoragePath: StoragePath
14    access(all) let StakingPublicPath: PublicPath
15    access(all) let StakingPrivatePath: PrivatePath
16    access(all) let AdminStoragePath: StoragePath
17
18    // Minimum staking duration (in seconds) before rewards begin accruing.
19    access(all) let minStakeDuration: UFix64
20
21    // Reward rates (Tit Tokens per second):
22    access(all) let rewardRateCarClubCommon: UFix64
23    access(all) let rewardRateCarClubRare: UFix64
24    access(all) let rewardRateCarClubSponsor: UFix64
25    access(all) let rewardRateCarClubLegendary: UFix64 
26    access(all) let rewardRateGenesis: UFix64 
27    access(all) let rewardRateTires: UFix64
28    access(all) let rewardRateHelmets: UFix64
29    access(all) let rewardRateWheels: UFix64
30    access(all) let rewardRateCars: UFix64
31
32    // Treasury vault holding Vroom Tokens used for rewards.
33    access(all) var treasuryVault: @VroomToken.Vault
34
35    // Staked NFT metadata storage
36    access(all) var stakedNFTMetadata: {Address: {UInt64: StakedNFTMetadata}}
37
38    // ✅ FIX: Use `{Address: {UInt64: @{NonFungibleToken.NFT}}}` to store actual NFTs
39    access(all) var stakedNFTAssets: @{Address: {UInt64: {NonFungibleToken.NFT}}}
40
41    // Store reward history for each NFT that was unstaked
42    access(all) var rewardHistory: {Address: {UInt64: UFix64}}
43
44    // Struct to hold staked NFT metadata.
45    access(all) struct StakedNFTMetadata {
46        access(all) let nftID: UInt64
47        access(all) let nftType: String
48        access(all) let nftName: String
49        access(self) var stakeTimestamp: UFix64  // Private to struct, mutable within the struct
50        access(all) var boost: UFix64
51        access(all) let thumbnail: String
52
53        init(nftID: UInt64, nftType: String, nftName: String, stakeTimestamp: UFix64, boost: UFix64, thumbnail: String) {
54            self.nftID = nftID
55            self.nftType = nftType
56            self.nftName = nftName
57            self.stakeTimestamp = stakeTimestamp
58            self.boost = boost
59            self.thumbnail = thumbnail
60        }
61
62        // Setter function to modify stakeTimestamp
63        access(all) fun updateStakeTimestamp(newTimestamp: UFix64) {
64            self.stakeTimestamp = newTimestamp
65        }
66
67        // ✅ Getter function to access stakeTimestamp
68        access(all) fun getStakeTimestamp(): UFix64 {
69            return self.stakeTimestamp
70        }
71    }
72
73    // ✅ Calculate CarClub Boost Multiplier
74    access(all) fun calcBoost(owner: Address): UFix64 {
75        let account = getAccount(owner)
76
77        let collectionRef = getAccount(owner).capabilities.borrow<&{NonFungibleToken.Collection}>(/public/CarClubCollection)
78            ?? panic("Could not borrow the reference to the CarClub collection")
79
80        let nftCount = collectionRef.getIDs().length
81
82        if nftCount >= 15 {
83            return 2.0
84        } else if nftCount >= 7 {
85            return 1.5
86        }
87        return 1.0
88    }
89
90
91        // Events
92
93    access(all) event NFTStaked(owner: Address, nftID: UInt64, nftType: String, nftName: String, stakeTimestamp: UFix64)
94    access(all) event NFTUnstaked(owner: Address, nftID: UInt64)
95    access(all) event RewardsClaimed(owner: Address, totalReward: UFix64)
96    access(all) event TreasuryFunded(newBalance: UFix64)
97
98    access(all) resource interface DriverzStakePublic {
99        access(all) fun stakeNFT(nft: @{NonFungibleToken.NFT}, nftID: UInt64, nftType: String, nftName: String, owner: Address, thumbnail: String) 
100        access(all) fun calculateRewards(owner: Address): UFix64
101        access(all) fun getStakedNFTDetails(owner: Address): [StakedNFTMetadata]
102    }
103
104    access(all) resource DriverzStake: DriverzStakePublic {
105
106
107        // 🔒 Secure stake function (No owner parameter)
108        access(all) fun stakeNFT(
109            nft: @{NonFungibleToken.NFT},
110            nftID: UInt64,
111            nftType: String,
112            nftName: String,
113            owner: Address,
114            thumbnail: String
115        ) {
116            let currentTime = getCurrentBlock().timestamp
117
118            // ✅ Get Boost Multiplier from calcBoost function
119            let boostMultiplier = DriverzStakingXx.calcBoost(owner: owner)
120
121            // ✅ Store the boost directly from the argument
122            var metadataDict = DriverzStakingXx.stakedNFTMetadata[owner] ?? {}
123            metadataDict[nftID] = StakedNFTMetadata(
124                nftID: nftID,
125                nftType: nftType,
126                nftName: nftName,
127                stakeTimestamp: currentTime,
128                boost: boostMultiplier,   // Store the boost here directly
129                thumbnail: thumbnail
130            )
131            DriverzStakingXx.stakedNFTMetadata[owner] = metadataDict
132
133            let maybeAssetsDict <- DriverzStakingXx.stakedNFTAssets.remove(key: owner)
134                ?? panic("No existing staked assets found for this owner.")
135
136            var assetsDict <- maybeAssetsDict
137            assetsDict[nftID] <-! nft
138            DriverzStakingXx.stakedNFTAssets[owner] <-! assetsDict
139
140            emit NFTStaked(owner: owner, nftID: nftID, nftType: nftType, nftName: nftName, stakeTimestamp: currentTime)
141        }
142
143        access(all) fun unstakeNFT(nftID: UInt64, nftType: String, owner: Address): @{NonFungibleToken.NFT} {
144            let maybeAssetsDict <- DriverzStakingXx.stakedNFTAssets.remove(key: owner)
145                ?? panic("No staked NFT assets found for owner")
146            var assetsDict <- maybeAssetsDict
147            let nft <- assetsDict.remove(key: nftID)
148                ?? panic("NFT asset not found")
149
150            if assetsDict.length > 0 {
151                DriverzStakingXx.stakedNFTAssets[owner] <-! assetsDict
152            } else {
153                destroy assetsDict
154            }
155
156            var metadataDict = DriverzStakingXx.stakedNFTMetadata[owner]
157                ?? panic("No staked NFT metadata found for owner")
158            let nftMetadata = metadataDict[nftID] 
159                ?? panic("NFT metadata not found")
160
161            // Check if the nftType matches
162            if nftMetadata.nftType != nftType {
163                panic("NFT type mismatch during unstaking")
164            }
165
166            // Fetch the CarClub Boost Multiplier
167            let multiplier = DriverzStakingXx.calcBoost(owner: owner) // ✅ Now using calcBoost
168
169            // Calculate and store the accumulated rewards for this NFT
170            let elapsed = getCurrentBlock().timestamp - nftMetadata.getStakeTimestamp()
171
172            var baseReward: UFix64 = 0.0
173            
174            // ✅ Fixed: Using standard if-else block for baseReward calculation
175            if nftMetadata.nftType == "genesis" {
176                baseReward = elapsed * DriverzStakingXx.rewardRateGenesis
177            } else if nftMetadata.nftType == "tires" {
178                baseReward = elapsed * DriverzStakingXx.rewardRateTires
179            } else if nftMetadata.nftType == "wheels" {
180                baseReward = elapsed * DriverzStakingXx.rewardRateWheels
181            } else if nftMetadata.nftType == "helmets" {
182                baseReward = elapsed * DriverzStakingXx.rewardRateHelmets
183            } else if nftMetadata.nftType == "cars" {
184                baseReward = elapsed * DriverzStakingXx.rewardRateCars
185            } else if nftMetadata.nftType == "carclubcommon" {
186                baseReward = elapsed * DriverzStakingXx.rewardRateCarClubCommon
187            } else if nftMetadata.nftType == "carclubrare" {
188                baseReward = elapsed * DriverzStakingXx.rewardRateCarClubRare
189            } else if nftMetadata.nftType == "carclubsponsor" {
190                baseReward = elapsed * DriverzStakingXx.rewardRateCarClubSponsor
191            } else if nftMetadata.nftType == "carclublegendary" {
192                baseReward = elapsed * DriverzStakingXx.rewardRateCarClubLegendary
193            }
194
195            // ✅ Apply the Boost Multiplier from CarClub NFTs
196            let totalReward = baseReward * multiplier
197
198            // Get the existing reward history for the owner
199            var historyDict = DriverzStakingXx.rewardHistory[owner] ?? {}
200
201            // Update or add the reward for this specific NFT
202            historyDict[nftID] = (historyDict[nftID] ?? 0.0) + totalReward
203
204            // Save the updated reward history back to the contract state
205            DriverzStakingXx.rewardHistory[owner] = historyDict
206
207            metadataDict.remove(key: nftID) 
208            DriverzStakingXx.stakedNFTMetadata[owner] = metadataDict
209
210            emit NFTUnstaked(owner: owner, nftID: nftID)
211            return <- nft
212        }
213
214
215        // 🔒 Secure claim rewards function (Only owner can call)
216        access(all) fun claimRewards(recipient: &{FungibleToken.Receiver}, owner: Address): @VroomToken.Vault {
217            let currentTime = getCurrentBlock().timestamp
218            var totalReward: UFix64 = 0.0
219
220            // Fetch the CarClub Boost Multiplier
221            let boostMultiplier = DriverzStakingXx.calcBoost(owner: owner)
222
223            // Fetch staked NFT metadata
224            var metadataDict = DriverzStakingXx.stakedNFTMetadata[owner] 
225                ?? panic("No staked NFT metadata found for owner")
226
227            var updatedMetadataDict: {UInt64: StakedNFTMetadata} = {}
228
229            for nftID in metadataDict.keys {
230                var metadata = metadataDict[nftID]!
231                let elapsed = currentTime - metadata.getStakeTimestamp()
232
233                if elapsed >= DriverzStakingXx.minStakeDuration {
234                    var baseReward: UFix64 = 0.0
235
236                    // ✅ Update rewards calculation for new NFT types
237                    if metadata.nftType == "genesis" {
238                        baseReward = elapsed * DriverzStakingXx.rewardRateGenesis
239                    } else if metadata.nftType == "tires" {
240                        baseReward = elapsed * DriverzStakingXx.rewardRateTires
241                    } else if metadata.nftType == "wheels" {
242                        baseReward = elapsed * DriverzStakingXx.rewardRateWheels
243                    } else if metadata.nftType == "helmets" {
244                        baseReward = elapsed * DriverzStakingXx.rewardRateHelmets
245                    } else if metadata.nftType == "cars" {
246                        baseReward = elapsed * DriverzStakingXx.rewardRateCars
247                    } else if metadata.nftType == "carclubcommon" {
248                        baseReward = elapsed * DriverzStakingXx.rewardRateCarClubCommon
249                    } else if metadata.nftType == "carclubrare" {
250                        baseReward = elapsed * DriverzStakingXx.rewardRateCarClubRare
251                    } else if metadata.nftType == "carclubsponsor" {
252                        baseReward = elapsed * DriverzStakingXx.rewardRateCarClubSponsor
253                    } else if metadata.nftType == "carclublegendary" {
254                        baseReward = elapsed * DriverzStakingXx.rewardRateCarClubLegendary
255                    }
256
257                    // ✅ Apply the CarClub Boost Multiplier
258                    let totalNFTReward = baseReward * boostMultiplier
259
260                    totalReward = totalReward + totalNFTReward
261
262                    // ✅ ✅ ✅ Update metadata timestamp after claiming
263                    let newMetadata = StakedNFTMetadata(
264                        nftID: metadata.nftID,
265                        nftType: metadata.nftType,
266                        nftName: metadata.nftName,
267                        stakeTimestamp: currentTime,  // 🔥 Reset timestamp after claiming rewards
268                        boost: metadata.boost,
269                        thumbnail: metadata.thumbnail
270                    )
271
272                    updatedMetadataDict[nftID] = newMetadata
273                } else {
274                    updatedMetadataDict[nftID] = metadata
275                }
276            }
277
278            // ✅ Overwrite storage with the updated dictionary
279            DriverzStakingXx.stakedNFTMetadata[owner] = updatedMetadataDict
280
281            // ✅ Also include rewards from unstaked NFTs
282            let historyDict = DriverzStakingXx.rewardHistory[owner] ?? {}
283            for reward in historyDict.values {
284                totalReward = totalReward + reward
285            }
286
287            // 🔥 Clear reward history after successfully claiming
288            if totalReward > 0.0 {
289                DriverzStakingXx.rewardHistory[owner] = {}
290            }
291
292            // Handle the token transfer
293            if totalReward <= 0.0 {
294                panic("No rewards available or minimum staking duration not met")
295            }
296            if DriverzStakingXx.treasuryVault.balance < totalReward {
297                panic("Insufficient funds in treasury")
298            }
299
300            let rewardVault <- DriverzStakingXx.treasuryVault.withdraw(amount: totalReward)
301
302            emit RewardsClaimed(owner: owner, totalReward: totalReward)
303            recipient.deposit(from: <- rewardVault)
304
305            return <- VroomToken.createEmptyVault(vaultType: Type<@VroomToken.Vault>())
306        }
307
308
309        // 🔒 Secure reward calculation
310        access(all) fun calculateRewards(owner: Address): UFix64 {
311            let currentTime = getCurrentBlock().timestamp
312            var totalReward: UFix64 = 0.0
313
314            // Fetch the boost multiplier based on CarClub NFTs owned
315            let boostMultiplier = DriverzStakingXx.calcBoost(owner: owner)
316
317            // Calculate rewards for currently staked NFTs
318            let metadataDict = DriverzStakingXx.stakedNFTMetadata[owner] 
319                ?? panic("No staked NFT metadata for owner")
320
321            for metadata in metadataDict.values {
322                let elapsed = currentTime - metadata.getStakeTimestamp()
323                if elapsed >= DriverzStakingXx.minStakeDuration {
324                    var baseReward: UFix64 = 0.0
325
326                    // Assign reward rates based on NFT type
327                    if metadata.nftType == "genesis" {
328                        baseReward = elapsed * DriverzStakingXx.rewardRateGenesis
329                    } else if metadata.nftType == "tires" {
330                        baseReward = elapsed * DriverzStakingXx.rewardRateTires
331                    } else if metadata.nftType == "wheels" {
332                        baseReward = elapsed * DriverzStakingXx.rewardRateWheels
333                    } else if metadata.nftType == "helmets" {
334                        baseReward = elapsed * DriverzStakingXx.rewardRateHelmets
335                    } else if metadata.nftType == "cars" {
336                        baseReward = elapsed * DriverzStakingXx.rewardRateCars
337                    } else if metadata.nftType == "carclubcommon" {
338                        baseReward = elapsed * DriverzStakingXx.rewardRateCarClubCommon
339                    } else if metadata.nftType == "carclubrare" {
340                        baseReward = elapsed * DriverzStakingXx.rewardRateCarClubRare
341                    } else if metadata.nftType == "carclubsponsor" {
342                        baseReward = elapsed * DriverzStakingXx.rewardRateCarClubSponsor
343                    } else if metadata.nftType == "carclublegendary" {
344                        baseReward = elapsed * DriverzStakingXx.rewardRateCarClubLegendary
345                    }
346
347                    // Apply boost multiplier
348                    let totalNFTReward = baseReward * boostMultiplier
349
350                    totalReward = totalReward + totalNFTReward
351                }
352            }
353
354            // ✅ Include rewards from previously unstaked NFTs
355            let historyDict = DriverzStakingXx.rewardHistory[owner] ?? {}
356            for reward in historyDict.values {
357                totalReward = totalReward + reward
358            }
359
360            return totalReward
361        }
362
363
364
365            // Retrieve details of all staked NFTs for a given owner.
366        access(all) fun getStakedNFTDetails(owner: Address): [StakedNFTMetadata] {
367            let metadataDict = DriverzStakingXx.stakedNFTMetadata[owner] ?? {}
368            var details: [StakedNFTMetadata] = []
369            for metadata in metadataDict.values {
370                details.append(metadata)
371            }
372            return details
373        }
374
375        access(all) fun createDriverzStakingXx(): @DriverzStake {
376            return <- create DriverzStakingXx.DriverzStake()
377        }
378    }
379
380    access(all) fun createDriverzStakingXx(owner: Address): @DriverzStake {
381        return <- create DriverzStake()
382    }
383
384        // Initialization
385    init(
386
387    ) {
388        self.StakingStoragePath = /storage/driverzStakingXStorage
389        self.StakingPublicPath = /public/driverzStakingXPublic
390        self.StakingPrivatePath = /private/driverzStakingXPrivate
391        self.AdminStoragePath = /storage/driverzStakingXAdmin
392
393            // Use the no-argument version of createEmptyVault()
394        self.treasuryVault <- VroomToken.createEmptyVault(vaultType: Type<@VroomToken.Vault>())
395        self.minStakeDuration = 1.0
396        self.rewardRateGenesis = 0.00000724
397        self.rewardRateTires = 0.00000434
398        self.rewardRateHelmets = 0.00000579
399        self.rewardRateWheels = 0.00000868
400        self.rewardRateCars = 0.00002228
401        self.rewardRateCarClubCommon = 0.00002894
402        self.rewardRateCarClubRare = 0.00005788
403        self.rewardRateCarClubSponsor = 0.00008682
404        self.rewardRateCarClubLegendary = 0.00014470
405        self.rewardHistory = {}
406        self.stakedNFTMetadata = {}
407        self.stakedNFTAssets <- {} as @{Address: {UInt64: {NonFungibleToken.NFT}}}
408    }
409
410
411    access(all) fun depositToTreasury(from: @VroomToken.Vault) {
412        self.treasuryVault.deposit(from: <- from)
413        emit TreasuryFunded(newBalance: self.treasuryVault.balance)
414    }
415
416    access(all) fun hasAssets(owner: Address): Bool {
417        return self.stakedNFTAssets[owner] != nil
418    }
419
420
421    access(all) fun createAssetsDictionary(owner: Address) {
422        // Overwrite any existing dictionary by forcefully setting a new empty one
423        self.stakedNFTAssets[owner] <-! {} as @{UInt64: {NonFungibleToken.NFT}}
424    }
425}