Smart Contract

Staking

A.807c3d470888cc48.Staking

Valid From

86,621,889

Deployed

1w ago
Feb 19, 2026, 10:24:09 AM UTC

Dependents

8 imports
1
2
3// SPDX-License-Identifier: MIT
4import NonFungibleToken from 0x1d7e57aa55817448
5import FungibleToken from 0xf233dcee88fe0abe
6import MetadataViews from 0x1d7e57aa55817448
7import Flunks from 0x807c3d470888cc48
8import Backpack from 0x807c3d470888cc48
9import GUM from 0x807c3d470888cc48
10import HybridCustodyHelper from 0x807c3d470888cc48
11import GUMStakingTracker from 0x807c3d470888cc48
12
13access(all)
14contract Staking{ 
15	access(all)
16	let AdminStoragePath: StoragePath
17	
18	access(self)
19	var Pools: @{String: Pool} // {poolName: Pool}
20	
21	
22	access(all)
23	event Stake(id: UInt64, pool: String)
24	
25	access(all)
26	event Unstake(id: UInt64, pool: String)
27	
28	access(all)
29	event ClaimReward(pool: String, amount: UFix64, tokenID: UInt64)
30	
31	access(all)
32	resource Pool{ 
33		access(all)
34		let name: String
35		
36		access(all)
37		var multiplier: UFix64
38		
39		access(self)
40		var stakedInfo:{ UInt64: StakingInfo} // {tokenID: StakingInfo}
41		
42		
43		access(all)
44		fun getStakedInfoByTokenID(tokenID: UInt64): StakingInfo?{ 
45			return self.stakedInfo[tokenID]
46		}
47		
48		access(all)
49		fun mutateStakingInfo(tokenID: UInt64, stakingInfo: StakingInfo?){ 
50			if stakingInfo == nil{ 
51				// If new staking info is nil, remove the existing info
52				self.stakedInfo.remove(key: tokenID)
53				return
54			} else{ 
55				// Otherwise update the existing info
56				self.stakedInfo[tokenID] = stakingInfo
57			}
58		}
59		
60		// Admin function to claim GUMS on behalf of users, GUM will go to the staker's wallet if it's still the owner at the time called
61		access(contract)
62		fun adminClaimOne(tokenID: UInt64, staker: Address){ 
63			if !self.stakedInfo.keys.contains(tokenID){ 
64				// 1. Not staked, return directly
65				return
66			}
67			let stakingInfo = self.stakedInfo[tokenID]
68			if stakingInfo == nil{ 
69				// 2. Not staked, return directly
70				return
71			}
72			if (stakingInfo!).staker != staker{ 
73				// 3. Staker not owner of the token
74				return
75			}
76			
77			// Verify the staker still ownes the token, return silently if not
78			if self.name == "Flunks"{ 
79				let ownedTokenIDs = HybridCustodyHelper.getFlunksTokenIDsFromAllLinkedAccounts(ownerAddress: staker)
80				if !ownedTokenIDs.contains(tokenID){ 
81					return
82				}
83			} else if self.name == "Backpack"{ 
84				let ownedTokenIDs = HybridCustodyHelper.getBackpackTokenIDsFromAllLinkedAccounts(ownerAddress: staker)
85				if !ownedTokenIDs.contains(tokenID){ 
86					return
87				}
88			}
89			
90			// Consolidate Rewards
91			let validRewardPeriodInDays = (stakingInfo!).validRewardPeriodInDays()
92			if validRewardPeriodInDays <= 0.0{ 
93				// 4. No reward to claim, return directly
94				return
95			}
96			var stakingReward =
97				Staking.pendingRewards(pool: self.name, ownerAddress: staker, tokenID: tokenID)
98			let GUMMinter =
99				Staking.account.storage.borrow<&GUM.Minter>(from: GUM.AdminStoragePath)
100				?? panic("Could not borrow reference to the GUM Minter resource")
101			
102			// Reset staking timestamp to the last reward timestamp
103			let newStakingInfo =
104				StakingInfo(
105					_staker: (stakingInfo!).staker,
106					_tokenID: (stakingInfo!).tokenID,
107					_stakedAtInSeconds: UInt64(getCurrentBlock().timestamp) + 60,
108					_pool: (stakingInfo!).pool
109				)
110			self.stakedInfo[tokenID] = newStakingInfo
111			emit ClaimReward(
112				pool: newStakingInfo.pool,
113				amount: stakingReward,
114				tokenID: newStakingInfo.tokenID
115			)
116			Staking.updateTracker(pool: self.name, tokenID: tokenID, amount: stakingReward)
117			
118			// Mint the specified amount of GUM tokens
119			let newVault <- GUMMinter.mintTokens(amount: stakingReward)
120			// Deposit the minted tokens into the recipient's Vault
121			let recipientVault =
122				getAccount(staker).capabilities.get<&{FungibleToken.Receiver}>(
123					GUM.VaultReceiverPublicPath
124				).borrow()
125				?? panic("Could not borrow reference to the recipient's Vault")
126			recipientVault.deposit(from: <-newVault)
127		}
128		
129		access(contract)
130		fun adminClaimAll(tokenIDs: [UInt64], staker: Address){ 
131			for tokenID in tokenIDs{ 
132				self.adminClaimOne(tokenID: tokenID, staker: staker)
133			}
134		}
135		
136		access(all)
137		fun claimOne(signerAuth: auth(SaveValue, Capabilities, Storage, BorrowValue) &Account, tokenID: UInt64){ 
138			if !self.stakedInfo.keys.contains(tokenID){ 
139				// 1. Not staked, return directly
140				return
141			}
142			let stakingInfo = self.stakedInfo[tokenID]
143			if stakingInfo == nil{ 
144				// 2. Not staked, return directly
145				return
146			}
147			if (stakingInfo!).staker != signerAuth.address{ 
148				// 3. staker is not owner, check if the signer is the new owner of the staked NFT
149				if self.name == "Flunks"{ 
150					let ownedTokenIDs = HybridCustodyHelper.getFlunksTokenIDsFromAllLinkedAccounts(ownerAddress: signerAuth.address)
151					if !ownedTokenIDs.contains(tokenID){ 
152						return
153					}
154				} else if self.name == "Backpack"{ 
155					let ownedTokenIDs = HybridCustodyHelper.getBackpackTokenIDsFromAllLinkedAccounts(ownerAddress: signerAuth.address)
156					if !ownedTokenIDs.contains(tokenID){ 
157						return
158					}
159				}
160				
161				// if not, return directly
162				return
163			}
164			
165			// Consolidate Rewards
166			let validRewardPeriodInDays = (stakingInfo!).validRewardPeriodInDays()
167			if validRewardPeriodInDays <= 0.0{ 
168				// 4. No reward to claim, return directly
169				return
170			}
171			
172			// Setup $GUM Vault if user does not already have one
173			Staking.setupGUMVault(signer: signerAuth)
174			let recipient = getAccount(signerAuth.address)
175			let recipientVault =
176				signerAuth.capabilities.get<&{FungibleToken.Receiver}>(
177					GUM.VaultReceiverPublicPath
178				).borrow() ?? panic("Could not borrow reference to the recipient's Vault")
179			var stakingReward =
180				Staking.pendingRewards(
181					pool: self.name,
182					ownerAddress: signerAuth.address,
183					tokenID: tokenID
184				)
185			let GUMMinter =
186				Staking.account.storage.borrow<&GUM.Minter>(from: GUM.AdminStoragePath)
187				?? panic("Could not borrow reference to the GUM Minter resource")
188			
189			// Reset staking timestamp to the last reward timestamp
190			let newStakingInfo =
191				StakingInfo(
192					_staker: (stakingInfo!).staker,
193					_tokenID: (stakingInfo!).tokenID,
194					_stakedAtInSeconds: UInt64(getCurrentBlock().timestamp) + 60,
195					_pool: (stakingInfo!).pool
196				)
197			self.stakedInfo[tokenID] = newStakingInfo
198			emit ClaimReward(
199				pool: newStakingInfo.pool,
200				amount: stakingReward,
201				tokenID: newStakingInfo.tokenID
202			)
203			Staking.updateTracker(pool: self.name, tokenID: tokenID, amount: stakingReward)
204			
205			// Mint the specified amount of GUM tokens
206			let newVault <- GUMMinter.mintTokens(amount: stakingReward)
207			// Deposit the minted tokens into the recipient's Vault
208			recipientVault.deposit(from: <-newVault)
209		}
210		
211		access(all)
212		fun claimAll(signerAuth: auth(SaveValue, Capabilities, Storage, BorrowValue) &Account, tokenIDs: [UInt64]){ 
213			// Force re-link collections
214			HybridCustodyHelper.forceRelinkCollections(signer: signerAuth)
215			for tokenID in tokenIDs{ 
216				self.claimOne(signerAuth: signerAuth, tokenID: tokenID)
217			}
218		}
219		
220		access(all)
221		fun claimMany(signerAuth: auth(SaveValue, Capabilities, Storage, BorrowValue) &Account, tokenIDs: [UInt64]){ 
222			// Force re-link collections
223			HybridCustodyHelper.forceRelinkCollections(signer: signerAuth)
224			for tokenID in tokenIDs{ 
225				self.claimOne(signerAuth: signerAuth, tokenID: tokenID)
226			}
227		}
228		
229		init(_name: String, _multiplier: UFix64){ 
230			self.name = _name
231			self.multiplier = _multiplier
232			self.stakedInfo ={} 
233		}
234	}
235	
236	access(all)
237	struct StakingInfo{ 
238		access(all)
239		let staker: Address
240		
241		access(all)
242		let tokenID: UInt64
243		
244		access(all)
245		let stakedAtInSeconds: UInt64
246		
247		access(all)
248		let pool: String
249		
250		access(all)
251		fun validRewardPeriodInDays(): UFix64{ 
252			if UInt64(getCurrentBlock().timestamp) <= self.stakedAtInSeconds{ 
253				return 0.0
254			}
255			
256			// Calculate the time difference in seconds
257			let timeDiffInSeconds = UInt64(getCurrentBlock().timestamp) - self.stakedAtInSeconds
258			
259			// Convert the time difference to UFix64 for decimal calculation
260			let timeDiffInSecondsDecimal = UFix64(timeDiffInSeconds)
261			
262			// Number of seconds in a day (as UFix64 for decimal precision)
263			let secondsInADay = 86400.0
264			
265			// Calculate the time difference in days as a decimal
266			let daysDecimal = timeDiffInSecondsDecimal / secondsInADay
267			return daysDecimal
268		}
269		
270		init(_staker: Address, _tokenID: UInt64, _stakedAtInSeconds: UInt64, _pool: String){ 
271			self.staker = _staker
272			self.tokenID = _tokenID
273			self.stakedAtInSeconds = _stakedAtInSeconds
274			self.pool = _pool
275		}
276	}
277	
278	// Dumb helper
279	access(all)
280	fun validateFlunkOwner(ownerAddress: Address, tokenID: UInt64): Bool{ 
281		let tokenIDs =
282			HybridCustodyHelper.getFlunksTokenIDsFromAllLinkedAccounts(ownerAddress: ownerAddress)
283		return tokenIDs.contains(tokenID)
284	}
285	
286	// Dumb helper
287	access(all)
288	fun validateBackpackOwner(ownerAddress: Address, tokenID: UInt64): Bool{ 
289		let tokenIDs =
290			HybridCustodyHelper.getBackpackTokenIDsFromAllLinkedAccounts(ownerAddress: ownerAddress)
291		return tokenIDs.contains(tokenID)
292	}
293	
294	// Dumb helper
295	access(all)
296	fun validateOwnership(pool: String, ownerAddress: Address, tokenID: UInt64): Bool{ 
297		if pool == "Flunks"{ 
298			return Staking.validateFlunkOwner(ownerAddress: ownerAddress, tokenID: tokenID)
299		} else if pool == "Backpack"{ 
300			return Staking.validateBackpackOwner(ownerAddress: ownerAddress, tokenID: tokenID)
301		}
302		return false
303	}
304	
305	// Dumb helper
306	access(all)
307	fun getStakingInfo(
308		signerAddress: Address,
309		pool: String,
310		tokenID: UInt64
311	): Staking.StakingInfo?{ 
312		let selfAdmin =
313			self.account.storage.borrow<&Staking.Admin>(from: Staking.AdminStoragePath)
314			?? panic("Could not borrow a reference to the NonFungibleGUM Admin")
315		let pool = selfAdmin.borrowPool(pool: pool)
316		return pool.getStakedInfoByTokenID(tokenID: tokenID)
317	}
318	
319	access(all)
320	fun stakeOne(signerAuth: auth(SaveValue, Capabilities, Storage, BorrowValue) &Account, pool: String, tokenID: UInt64){ 
321		let isValid = Staking.validateOwnership(pool: pool, ownerAddress: signerAuth.address, tokenID: tokenID)
322		if (!isValid){ 
323			panic("Not owner")
324		}
325
326		let existingStakingInfo =
327			Staking.getStakingInfo(signerAddress: signerAuth.address, pool: pool, tokenID: tokenID)
328		if existingStakingInfo != nil && (existingStakingInfo!).staker == signerAuth.address{ 
329			// Already staked by the user, just return silently
330			return
331		}
332		
333		// Otherwise stake it fresh
334		let stakingInfo =
335			StakingInfo(
336				_staker: signerAuth.address,
337				_tokenID: tokenID,
338				_stakedAtInSeconds: UInt64(getCurrentBlock().timestamp) + 60,
339				_pool: pool
340			)
341		let selfAdmin =
342			self.account.storage.borrow<&Staking.Admin>(from: Staking.AdminStoragePath)
343			?? panic("Could not borrow a reference to the NonFungibleGUM Admin")
344		let poolRef = selfAdmin.borrowPool(pool: pool)
345		poolRef.mutateStakingInfo(tokenID: tokenID, stakingInfo: stakingInfo)
346		emit Stake(id: tokenID, pool: pool)
347	}
348	
349	access(all)
350	fun unstakeOne(signerAuth: auth(SaveValue, Capabilities, Storage, BorrowValue) &Account, pool: String, tokenID: UInt64){ 
351		let isValid = Staking.validateOwnership(pool: pool, ownerAddress: signerAuth.address, tokenID: tokenID)
352		if (!isValid){ 
353			panic("Not owner")
354		}
355
356		let existingStakingInfo =
357			Staking.getStakingInfo(signerAddress: signerAuth.address, pool: pool, tokenID: tokenID)
358		if existingStakingInfo == nil{ 
359			// Not staked, return silently
360			return
361		}
362		
363		// Claim the rewards for the user before unstaking
364		Staking.claimOne(signerAuth: signerAuth, pool: pool, tokenID: tokenID)
365		let selfAdmin =
366			self.account.storage.borrow<&Staking.Admin>(from: Staking.AdminStoragePath)
367			?? panic("Could not borrow a reference to the NonFungibleGUM Admin")
368		let poolRef = selfAdmin.borrowPool(pool: pool)
369		poolRef.mutateStakingInfo(tokenID: tokenID, stakingInfo: nil)
370		emit Unstake(id: tokenID, pool: pool)
371	}
372	
373	access(all)
374	fun stakeAll(signerAuth: auth(SaveValue, Capabilities, Storage, BorrowValue) &Account){ 
375		let flunksTokenIDs =
376			HybridCustodyHelper.getFlunksTokenIDsFromAllLinkedAccounts(
377				ownerAddress: signerAuth.address
378			)
379		let backpackTokenIDs =
380			HybridCustodyHelper.getBackpackTokenIDsFromAllLinkedAccounts(
381				ownerAddress: signerAuth.address
382			)
383		if flunksTokenIDs != nil{ 
384			for tokenID in flunksTokenIDs!{ 
385				Staking.stakeOne(signerAuth: signerAuth, pool: "Flunks", tokenID: tokenID)
386			}
387		}
388		if backpackTokenIDs != nil{ 
389			for tokenID in backpackTokenIDs!{ 
390				Staking.stakeOne(signerAuth: signerAuth, pool: "Backpack", tokenID: tokenID)
391			}
392		}
393	}
394	
395	access(all)
396	fun stakeMany(signerAuth: auth(SaveValue, Capabilities, Storage, BorrowValue) &Account, pool: String, tokenIDs: [UInt64]){ 
397		for tokenID in tokenIDs{ 
398			Staking.stakeOne(signerAuth: signerAuth, pool: pool, tokenID: tokenID)
399		}
400	}
401	
402	access(all)
403	fun claimOne(signerAuth: auth(SaveValue, Capabilities, Storage, BorrowValue) &Account, pool: String, tokenID: UInt64){ 
404		let selfAdmin =
405			self.account.storage.borrow<&Staking.Admin>(from: Staking.AdminStoragePath)
406			?? panic("Could not borrow a reference to the NonFungibleGUM Admin")
407		let poolRef = selfAdmin.borrowPool(pool: pool)
408		if pool == "Flunks"{ 
409			let flunksTokenIDs = HybridCustodyHelper.getFlunksTokenIDsFromAllLinkedAccounts(ownerAddress: signerAuth.address)
410			if !flunksTokenIDs.contains(tokenID){ 
411				panic("tokenID not found in signer's collection")
412			}
413		} else if pool == "Backpack"{ 
414			let backpackTokenIDs = HybridCustodyHelper.getBackpackTokenIDsFromAllLinkedAccounts(ownerAddress: signerAuth.address)
415			if !backpackTokenIDs.contains(tokenID){ 
416				panic("tokenID not found in signer's collection")
417			}
418		}
419		poolRef.claimOne(signerAuth: signerAuth, tokenID: tokenID)
420	}
421	
422	access(all)
423	fun claimPool(pool: String, signerAuth: auth(SaveValue, Capabilities, Storage, BorrowValue) &Account){ 
424		// Claim all GUMS accumulated from a pool
425		let selfAdmin =
426			self.account.storage.borrow<&Staking.Admin>(from: Staking.AdminStoragePath)
427			?? panic("Could not borrow a reference to the NonFungibleGUM Admin")
428		let poolRef: &Staking.Pool = selfAdmin.borrowPool(pool: pool)
429		if pool == "Flunks"{ 
430			let flunksTokenIDs = HybridCustodyHelper.getFlunksTokenIDsFromAllLinkedAccounts(ownerAddress: signerAuth.address)
431			poolRef.claimAll(signerAuth: signerAuth, tokenIDs: flunksTokenIDs)
432		} else if pool == "Backpack"{ 
433			let backpackTokenIDs = HybridCustodyHelper.getBackpackTokenIDsFromAllLinkedAccounts(ownerAddress: signerAuth.address)
434			poolRef.claimAll(signerAuth: signerAuth, tokenIDs: backpackTokenIDs)
435		}
436	}
437	
438	access(all)
439	fun claimMany(signerAuth: auth(SaveValue, Capabilities, Storage, BorrowValue) &Account, pool: String, tokenIDs: [UInt64]){ 
440		// Claim GUMs from many NFTs accumulated from a pool
441		let selfAdmin =
442			self.account.storage.borrow<&Staking.Admin>(from: Staking.AdminStoragePath)
443			?? panic("Could not borrow a reference to the NonFungibleGUM Admin")
444		let poolRef: &Staking.Pool = selfAdmin.borrowPool(pool: pool)
445		poolRef.claimMany(signerAuth: signerAuth, tokenIDs: tokenIDs)
446	}
447	
448	access(all)
449	fun claimAll(signerAuth: auth(SaveValue, Capabilities, Storage, BorrowValue) &Account){ 
450		Staking.claimPool(pool: "Flunks", signerAuth: signerAuth)
451		Staking.claimPool(pool: "Backpack", signerAuth: signerAuth)
452	}
453	
454	access(all)
455	fun pendingRewards(pool: String, ownerAddress: Address, tokenID: UInt64): UFix64{ 
456		if !["Flunks", "Backpack"].contains(pool) {
457			panic("Invalid pool")
458		}
459
460		let selfAdmin =
461			self.account.storage.borrow<&Staking.Admin>(from: Staking.AdminStoragePath)
462			?? panic("Could not borrow a reference to the NonFungibleGUM Admin")
463		let poolRef = selfAdmin.borrowPool(pool: pool)
464		let stakingInfo = poolRef.getStakedInfoByTokenID(tokenID: tokenID)
465		let stakingPeriodInDays = stakingInfo?.validRewardPeriodInDays() ?? 0.0
466		if pool == "Flunks"{ 
467			// Flunks staking reward = 5.0 * stakingPeriodInDays
468			return poolRef.multiplier * stakingPeriodInDays
469		} else if pool == "Backpack"{ 
470			// Backpack staking reward = (1.0 + 0.1 * slot bonus)  * stakingPeriodInDays
471			let slots = HybridCustodyHelper.getBackpackSlots(ownerAddress: ownerAddress, tokenID: tokenID)
472			
473			// Invalid slot bonus, throw error
474			if slots < 0 || slots > 20{ 
475				panic("Invalid slot bonus found!")
476			}
477			
478			// Get slots from MetadataViews resolved traits
479			return poolRef.multiplier * stakingPeriodInDays + 0.1 * UFix64(slots) * stakingPeriodInDays
480		}
481		return poolRef.multiplier * stakingPeriodInDays
482	}
483	
484	access(all)
485	fun pendingRewardsPerWallet(address: Address): UFix64{ 
486		let flunksTokenIDs =
487			HybridCustodyHelper.getFlunksTokenIDsFromAllLinkedAccounts(ownerAddress: address)
488		let backpackTokenIDs =
489			HybridCustodyHelper.getBackpackTokenIDsFromAllLinkedAccounts(ownerAddress: address)
490		var totalRewards = 0.0
491		if flunksTokenIDs != nil{ 
492			for tokenID in flunksTokenIDs{ 
493				let pendingRewards = Staking.pendingRewards(pool: "Flunks", ownerAddress: address, tokenID: tokenID)
494				totalRewards = totalRewards + pendingRewards
495			}
496		}
497		if backpackTokenIDs != nil{ 
498			for tokenID in backpackTokenIDs{ 
499				let pendingRewards = Staking.pendingRewards(pool: "Backpack", ownerAddress: address, tokenID: tokenID)
500				totalRewards = totalRewards + pendingRewards
501			}
502		}
503		return totalRewards
504	}
505	
506	access(all)
507	fun setupGUMVault(signer: auth(SaveValue, Capabilities, Storage, BorrowValue) &Account){ 
508		// Check if the account already has a GUM Vault
509		if signer.storage.borrow<&GUM.Vault>(from: GUM.VaultStoragePath) == nil{ 
510			// If not, create a new empty Vault and save it in the account's storage
511			let newVault <- GUM.createEmptyVault(vaultType: Type<@GUM.Vault>())
512			signer.storage.save(<-newVault, to: GUM.VaultStoragePath)
513
514			let cap = signer.capabilities.storage.issue<&GUM.Vault>(GUM.VaultStoragePath)
515            signer.capabilities.publish(cap, at: GUM.VaultReceiverPublicPath)
516			signer.capabilities.publish(cap, at: GUM.VaultBalancePublicPath)
517		}
518
519		let balanceRef = getAccount(signer.address)
520        	.capabilities.borrow<&GUM.Vault>(GUM.VaultBalancePublicPath)
521		if balanceRef == nil{
522			signer.capabilities.unpublish(GUM.VaultReceiverPublicPath)
523			signer.capabilities.unpublish(GUM.VaultBalancePublicPath)
524
525			let cap = signer.capabilities.storage.issue<&GUM.Vault>(GUM.VaultStoragePath)
526			signer.capabilities.publish(cap, at: GUM.VaultReceiverPublicPath)
527			signer.capabilities.publish(cap, at: GUM.VaultBalancePublicPath)
528		}
529	}
530	
531	access(all)
532	resource Admin{ 
533		access(all)
534		fun borrowPool(pool: String): &Pool{ 
535			pre{ 
536				Staking.Pools[pool] != nil:
537					"Cannot borrow Pool: Pool with provided name does not exist"
538			}
539			return (&Staking.Pools[pool] as &Pool?)!
540		}
541		
542		access(all)
543		fun adminClaimPool(pool: String, staker: Address){ 
544			let poolRef = self.borrowPool(pool: pool)
545			if pool == "Flunks"{ 
546				let tokenIDs = getAccount(staker).capabilities.get<&Flunks.Collection>(Flunks.CollectionPublicPath).borrow()?.getIDs()
547				poolRef.adminClaimAll(tokenIDs: tokenIDs!, staker: staker)
548			} else if pool == "Backpack"{ 
549				let tokenIDs = getAccount(staker).capabilities.get<&Backpack.Collection>(Backpack.CollectionPublicPath).borrow()?.getIDs()
550				poolRef.adminClaimAll(tokenIDs: tokenIDs!, staker: staker)
551			}
552		}
553		
554		access(all)
555		fun adminClaimAll(staker: Address){ 
556			self.adminClaimPool(pool: "Flunks", staker: staker)
557			self.adminClaimPool(pool: "Backpack", staker: staker)
558		}
559		
560		access(all)
561		fun adminStakeOne(
562			stakerAddress: Address,
563			pool: String,
564			tokenID: UInt64,
565			stakedAtInSeconds: UInt64
566		){ 
567			// Validate stakedAtInSeconds has to be within 60 days
568			if UInt64(getCurrentBlock().timestamp) - stakedAtInSeconds > 5184000{ 
569				panic("StakedAtInSeconds cannot be more than 60 days in the past")
570			}
571			let poolRef = self.borrowPool(pool: pool)
572			let adminStakingInfo =
573				StakingInfo(
574					_staker: stakerAddress,
575					_tokenID: tokenID,
576					_stakedAtInSeconds: stakedAtInSeconds,
577					_pool: pool
578				)
579			poolRef.mutateStakingInfo(tokenID: tokenID, stakingInfo: adminStakingInfo)
580		}
581		
582		access(all)
583		fun adminStakeMany(
584			stakerAddress: Address,
585			pool: String,
586			tokenIDs: [
587				UInt64
588			],
589			stakedAtInSeconds: UInt64
590		){ 
591			for tokenID in tokenIDs{ 
592				self.adminStakeOne(stakerAddress: stakerAddress, pool: pool, tokenID: tokenID, stakedAtInSeconds: stakedAtInSeconds)
593			}
594		}
595	}
596	
597	access(contract)
598	fun updateTracker(pool: String, tokenID: UInt64, amount: UFix64){ 
599		let trackerAdmin =
600			Staking.account.storage.borrow<&GUMStakingTracker.Admin>(
601				from: GUMStakingTracker.AdminStoragePath
602			)
603			?? panic("Could not borrow a reference to the GUMStakingTracker Admin")
604		trackerAdmin.updateClaimedGUM(pool: pool, tokenID: tokenID, amount: amount)
605	}
606	
607	init(){ 
608		self.AdminStoragePath = /storage/StakingAdmin
609		self.Pools <-{ 
610				"Flunks": <-create Pool(_name: "Flunks", _multiplier: 5.0),
611				"Backpack": <-create Pool(_name: "Backpack", _multiplier: 1.0)
612			}
613		let admin <- create Admin()
614		self.account.storage.save(<-admin, to: self.AdminStoragePath)
615	}
616}
617