Smart Contract

DriverzStakingX

A.f887ece39166906e.DriverzStakingX

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