Smart Contract
DriverzStakingXx
A.f887ece39166906e.DriverzStakingXx
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}