Smart Contract

StarlyTokenStaking

A.76a9b420a331b9f0.StarlyTokenStaking

Deployed

3d ago
Feb 25, 2026, 12:50:17 AM UTC

Dependents

10 imports
1// Starly staking.
2//
3// Main features:
4//   * compound interests, periodic compounding with 1 second period
5//   * APY 15%, configurable
6//   * users can stake/unstake anytime
7//   * stakes can have min staking time in seconds
8//   * stake is basically a NFT that is stored in user's wallet
9//
10// Admin:
11//   * create custom stakes
12//   * ability to refund
13//
14// Configurable precautions:
15//   * master switches to enable/disable staking and unstaking
16//   * unstaking fees (flat and percent)
17//   * unstaking penalty (if fees > interest)
18//   * no unstaking fees after certain staking period
19//   * timestamp until unstaking is disabled
20
21import Burner from 0xf233dcee88fe0abe
22import CompoundInterest from 0x76a9b420a331b9f0
23import FungibleToken from 0xf233dcee88fe0abe
24import ViewResolver from 0x1d7e57aa55817448
25import MetadataViews from 0x1d7e57aa55817448
26import NonFungibleToken from 0x1d7e57aa55817448
27import StarlyToken from 0x142fa6570b62fd97
28
29access(all) contract StarlyTokenStaking: NonFungibleToken {
30
31    access(all) event TokensStaked(
32        id: UInt64,
33        address: Address?,
34        principal: UFix64,
35        stakeTimestamp: UFix64,
36        minStakingSeconds: UFix64,
37        k: UFix64)
38    access(all) event TokensUnstaked(
39        id: UInt64,
40        address: Address?,
41        amount: UFix64,
42        principal: UFix64,
43        interest: UFix64,
44        unstakingFees: UFix64,
45        stakeTimestamp: UFix64,
46        unstakeTimestamp: UFix64,
47        k: UFix64)
48    access(all) event TokensBurned(id: UInt64, principal: UFix64)
49    access(all) event Withdraw(id: UInt64, from: Address?)
50    access(all) event Deposit(id: UInt64, to: Address?)
51    access(all) event ContractInitialized()
52
53    access(all) var totalSupply: UInt64
54    access(all) var totalPrincipalStaked: UFix64
55    access(all) var totalInterestPaid: UFix64
56
57    access(all) var stakingEnabled: Bool
58    access(all) var unstakingEnabled: Bool
59
60    // the unstaking fees to unstake X tokens = unstakingFlatFee + unstakingFee * X
61    access(all) var unstakingFee: UFix64
62    access(all) var unstakingFlatFee: UFix64
63
64    // unstake without fees if staked for this amount of seconds
65    access(all) var unstakingFeesNotAppliedAfterSeconds: UFix64
66
67    // cannot unstake if not staked for this amount of seconds
68    access(all) var minStakingSeconds: UFix64
69
70    // minimal principal for stake
71    access(all) var minStakePrincipal: UFix64
72
73    // cannot unstake until this timestamp
74    access(all) var unstakingDisabledUntilTimestamp: UFix64
75
76    // k = log10(1+r), where r is per-second interest ratio, taken from CompoundInterest contract
77    access(all) var k: UFix64
78
79    access(all) let CollectionStoragePath: StoragePath
80    access(all) let CollectionPublicPath: PublicPath
81    access(all) let AdminStoragePath: StoragePath
82    access(all) let MinterStoragePath: StoragePath
83    access(all) let BurnerStoragePath: StoragePath
84
85    access(all) enum Tier: UInt8 {
86        access(all) case NoTier
87        access(all) case Silver
88        access(all) case Gold
89        access(all) case Platinum
90    }
91
92    access(all) resource interface StakePublic {
93        access(all) fun getPrincipal(): UFix64
94        access(all) fun getStakeTimestamp(): UFix64
95        access(all) fun getMinStakingSeconds(): UFix64
96        access(all) fun getK(): UFix64
97        access(all) fun getAccumulatedAmount(): UFix64
98        access(all) fun getUnstakingFees(): UFix64
99        access(all) fun canUnstake(): Bool
100    }
101
102    access(all) struct StakeMetadataView {
103        access(all) let id: UInt64
104        access(all) let principal: UFix64
105        access(all) let stakeTimestamp: UFix64
106        access(all) let minStakingSeconds: UFix64
107        access(all) let k: UFix64
108        access(all) let accumulatedAmount: UFix64
109        access(all) let canUnstake: Bool
110        access(all) let unstakingFees: UFix64
111
112        init(
113            id: UInt64,
114            principal: UFix64,
115            stakeTimestamp: UFix64,
116            minStakingSeconds: UFix64,
117            k: UFix64,
118            accumulatedAmount: UFix64,
119            canUnstake: Bool,
120            unstakingFees: UFix64) {
121            self.id = id
122            self.principal = principal
123            self.stakeTimestamp = stakeTimestamp
124            self.minStakingSeconds = minStakingSeconds
125            self.k = k
126            self.accumulatedAmount = accumulatedAmount
127            self.canUnstake = canUnstake
128            self.unstakingFees = unstakingFees
129        }
130    }
131
132    // Stake (named as NFT to comply with NonFungibleToken interface) contains the vault with staked tokens
133    access(all) resource NFT: NonFungibleToken.NFT, Burner.Burnable, StakePublic {
134        access(all) let id: UInt64
135        access(contract) let principalVault: @StarlyToken.Vault
136        access(all) let stakeTimestamp: UFix64
137        access(all) let minStakingSeconds: UFix64
138        access(all) let k: UFix64
139
140        init(
141            id: UInt64,
142            principalVault: @StarlyToken.Vault,
143            stakeTimestamp: UFix64,
144            minStakingSeconds: UFix64,
145            k: UFix64) {
146            self.id = id
147            self.principalVault <-principalVault
148            self.stakeTimestamp = stakeTimestamp
149            self.minStakingSeconds = minStakingSeconds
150            self.k = k
151        }
152
153        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
154            return <-StarlyTokenStaking.createEmptyCollection(nftType: Type<@StarlyTokenStaking.NFT>())
155        }
156
157        // if destroyed we destroy the tokens and decrease totalPrincipalStaked
158        access(contract) fun burnCallback() {
159            let principalAmount = self.principalVault.balance
160            if (principalAmount > 0.0) {
161                StarlyTokenStaking.totalPrincipalStaked = StarlyTokenStaking.totalPrincipalStaked - principalAmount
162                emit TokensBurned(id: self.id, principal: principalAmount)
163            }
164        }
165
166        access(all) view fun getViews(): [Type] {
167            return [
168                Type<MetadataViews.Display>(),
169                Type<StakeMetadataView>()
170            ]
171        }
172
173        access(all) fun resolveView(_ view: Type): AnyStruct? {
174            switch view {
175                case Type<MetadataViews.Display>():
176                    return MetadataViews.Display(
177                        name: "StarlyToken stake #".concat(self.id.toString()),
178                        description: "id: ".concat(self.id.toString())
179                            .concat(", principal: ").concat(self.principalVault.balance.toString())
180                            .concat(", k: ").concat(self.k.toString())
181                            .concat(", stakeTimestamp: ").concat(UInt64(self.stakeTimestamp).toString())
182                            .concat(", minStakingSeconds: ").concat(UInt64(self.minStakingSeconds).toString()),
183                        thumbnail: MetadataViews.HTTPFile(url: ""))
184                case Type<StakeMetadataView>():
185                    return StakeMetadataView(
186                        id: self.id,
187                        principal: self.principalVault.balance,
188                        stakeTimestamp: self.stakeTimestamp,
189                        minStakingSeconds: self.minStakingSeconds,
190                        k: self.k,
191                        accumulatedAmount: self.getAccumulatedAmount(),
192                        canUnstake: self.canUnstake(),
193                        unstakingFees: self.getUnstakingFees())
194            }
195            return nil
196        }
197
198        access(all) fun getPrincipal(): UFix64 {
199            return self.principalVault.balance
200        }
201
202        access(all) fun getStakeTimestamp(): UFix64 {
203            return self.stakeTimestamp
204        }
205
206        access(all) fun getMinStakingSeconds(): UFix64 {
207            return self.minStakingSeconds
208        }
209
210        access(all) fun getK(): UFix64 {
211            return self.k
212        }
213
214        access(all) fun getAccumulatedAmount(): UFix64 {
215            let timestamp = getCurrentBlock().timestamp
216            let seconds = timestamp - self.stakeTimestamp
217            return self.principalVault.balance * CompoundInterest.generatedCompoundInterest(seconds: seconds, k: self.k)
218        }
219
220        // calculate unstaking fees using current StarlyTokenStaking parameters
221        access(all) fun getUnstakingFees(): UFix64 {
222            return self.getUnstakingFeesInternal(
223                unstakingFee: StarlyTokenStaking.unstakingFee,
224                unstakingFlatFee: StarlyTokenStaking.unstakingFlatFee,
225                unstakingFeesNotAppliedAfterSeconds: StarlyTokenStaking.unstakingFeesNotAppliedAfterSeconds,
226            )
227        }
228
229        // ability to calculate unstaking fees using provided parameters
230        access(contract) fun getUnstakingFeesInternal(
231            unstakingFee: UFix64,
232            unstakingFlatFee: UFix64,
233            unstakingFeesNotAppliedAfterSeconds: UFix64,
234        ): UFix64 {
235            let timestamp = getCurrentBlock().timestamp
236            let seconds = timestamp - self.stakeTimestamp
237            if (seconds >= unstakingFeesNotAppliedAfterSeconds) {
238                return 0.0
239            } else {
240                let accumulatedAmount = self.getAccumulatedAmount()
241                return unstakingFlatFee + unstakingFee * accumulatedAmount
242            }
243        }
244
245        access(all) fun canUnstake(): Bool {
246            let timestamp = getCurrentBlock().timestamp
247            let seconds = timestamp - self.stakeTimestamp
248            if (timestamp < StarlyTokenStaking.unstakingDisabledUntilTimestamp
249                || seconds < self.minStakingSeconds
250                || seconds < StarlyTokenStaking.minStakingSeconds
251                || self.stakeTimestamp >= timestamp) {
252                return false
253            } else {
254                return true
255            }
256        }
257    }
258
259    // We put stake creation logic into minter, its job is to have checks, emit events, update counters
260    access(all) resource NFTMinter {
261
262        access(all) fun mintStake(
263            address: Address?,
264            principalVault: @StarlyToken.Vault,
265            stakeTimestamp: UFix64,
266            minStakingSeconds: UFix64,
267            k: UFix64): @StarlyTokenStaking.NFT {
268
269            pre {
270                StarlyTokenStaking.stakingEnabled: "Staking is disabled"
271                principalVault.balance > 0.0: "Principal cannot be zero"
272                principalVault.balance >= StarlyTokenStaking.minStakePrincipal: "Principal is too small"
273                k <= CompoundInterest.k2000: "K cannot be larger than 2000% APY"
274            }
275
276            let stake <- create NFT(
277                id: StarlyTokenStaking.totalSupply,
278                principalVault: <-principalVault,
279                stakeTimestamp: stakeTimestamp,
280                minStakingSeconds: minStakingSeconds,
281                k: k)
282            let principalAmount = stake.principalVault.balance
283            StarlyTokenStaking.totalSupply = StarlyTokenStaking.totalSupply + (1 as UInt64)
284            StarlyTokenStaking.totalPrincipalStaked = StarlyTokenStaking.totalPrincipalStaked + principalAmount
285            emit TokensStaked(
286                id: stake.id,
287                address: address,
288                principal: principalAmount,
289                stakeTimestamp: stakeTimestamp,
290                minStakingSeconds: minStakingSeconds,
291                k: stake.k)
292            return <-stake
293        }
294    }
295
296    // We put stake unstaking logic into burner, its job is to have checks, emit events, update counters
297    access(all) resource NFTBurner {
298
299        access(all) fun burnStake(
300            stake: @StarlyTokenStaking.NFT,
301            k: UFix64,
302            address: Address?,
303            minStakingSeconds: UFix64,
304            unstakingFee: UFix64,
305            unstakingFlatFee: UFix64,
306            unstakingFeesNotAppliedAfterSeconds: UFix64,
307            unstakingDisabledUntilTimestamp: UFix64): @StarlyToken.Vault {
308
309            pre {
310                StarlyTokenStaking.unstakingEnabled: "Unstaking is disabled"
311                k <= CompoundInterest.k2000: "K cannot be larger than 2000% APY"
312                stake.stakeTimestamp < getCurrentBlock().timestamp: "Cannot unstake stake with stakeTimestamp more or equal to current timestamp"
313            }
314
315            let timestamp = getCurrentBlock().timestamp
316            if (timestamp < unstakingDisabledUntilTimestamp) {
317                panic("Unstaking is disabled at the moment")
318            }
319            let seconds = timestamp - stake.stakeTimestamp
320            if (seconds < minStakingSeconds || seconds < stake.minStakingSeconds) {
321                panic("Staking period is too short")
322            }
323
324            let unstakingFees = stake.getUnstakingFeesInternal(
325                unstakingFee: unstakingFee,
326                unstakingFlatFee: unstakingFlatFee,
327                unstakingFeesNotAppliedAfterSeconds: unstakingFeesNotAppliedAfterSeconds,
328            )
329            let principalAmount = stake.principalVault.balance
330            let vault <- stake.principalVault.withdraw(amount: principalAmount) as! @StarlyToken.Vault
331            let compoundInterest = CompoundInterest.generatedCompoundInterest(seconds: seconds, k: k)
332            let interestAmount = principalAmount * compoundInterest - principalAmount
333
334            let interestVaultRef = StarlyTokenStaking.account.storage.borrow<auth(FungibleToken.Withdraw) &StarlyToken.Vault>(from: StarlyToken.TokenStoragePath)!
335            if (interestAmount > unstakingFees) {
336                let interestAmountMinusFees = interestAmount - unstakingFees
337                vault.deposit(from: <-interestVaultRef.withdraw(amount: interestAmountMinusFees))
338                StarlyTokenStaking.totalInterestPaid = StarlyTokenStaking.totalInterestPaid + interestAmountMinusFees
339            } else {
340                // if accumulated interest do not cover unstaking fees, user will pay penalty from principal vault
341                let penalty = unstakingFees - interestAmount
342                interestVaultRef.deposit(from: <-vault.withdraw(amount: penalty))
343            }
344            StarlyTokenStaking.totalPrincipalStaked = StarlyTokenStaking.totalPrincipalStaked - principalAmount
345            emit TokensUnstaked(
346                id: stake.id,
347                address: address,
348                amount: vault.balance,
349                principal: principalAmount,
350                interest: interestAmount,
351                unstakingFees: unstakingFees,
352                stakeTimestamp: stake.stakeTimestamp,
353                unstakeTimestamp: timestamp,
354                k: k)
355            Burner.burn(<-stake)
356            return <-vault
357        }
358    }
359
360    access(all) resource interface CollectionPublic {
361        access(all) fun borrowStakePublic(id: UInt64): &StarlyTokenStaking.NFT
362        access(all) fun getStakedAmount(): UFix64
363        access(all) fun getStakingTier(): Tier
364        // admin has to have the ability to refund stake
365        access(contract) fun refund(id: UInt64, k: UFix64)
366    }
367
368    access(all) resource interface CollectionPrivate {
369        access(all) fun borrowStakePrivate(id: UInt64): &StarlyTokenStaking.NFT
370        access(all) fun stake(principalVault: @StarlyToken.Vault)
371        access(all) fun unstake(id: UInt64): @StarlyToken.Vault
372        access(all) fun unstakeAll(): @StarlyToken.Vault
373    }
374
375    access(all) resource Collection:
376        NonFungibleToken.Collection,
377        CollectionPublic,
378        CollectionPrivate {
379
380        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
381
382        init () {
383            self.ownedNFTs <- {}
384        }
385
386        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
387            return <-StarlyTokenStaking.createEmptyCollection(nftType: Type<@StarlyTokenStaking.NFT>())
388        }
389
390        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
391            let supportedTypes: {Type: Bool} = {}
392            supportedTypes[Type<@StarlyTokenStaking.NFT>()] = true
393            return supportedTypes
394        }
395
396        access(all) view fun isSupportedNFTType(type: Type): Bool {
397           if type == Type<@StarlyTokenStaking.NFT>() {
398            return true
399           } else {
400            return false
401           }
402        }
403
404        access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
405            let stake <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
406            emit Withdraw(id: stake.id, from: self.owner?.address)
407            return <-stake
408        }
409
410        access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
411            let token <- token as! @StarlyTokenStaking.NFT
412            let oldToken <- self.ownedNFTs[token.id] <- token
413            destroy oldToken
414        }
415
416        access(all) fun getStakedAmount(): UFix64 {
417            var sum: UFix64 = 0.0
418            for nftId in self.ownedNFTs.keys {
419                let nft = (&self.ownedNFTs[nftId] as &{NonFungibleToken.NFT}?)!
420                let stake = nft as! &StarlyTokenStaking.NFT
421                sum = sum + stake.getAccumulatedAmount()
422            }
423            return sum
424        }
425
426        access(all) fun getStakingTier(): Tier {
427            var sum = self.getStakedAmount()
428
429            if sum >= 50000.0 {
430                return Tier.Platinum
431            } else if sum >= 10000.0 {
432                return Tier.Gold
433            } else if sum >= 1000.0 {
434                return Tier.Silver
435            } else {
436                return Tier.NoTier
437            }
438        }
439
440        access(all) view fun getIDs(): [UInt64] {
441            return self.ownedNFTs.keys
442        }
443
444        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
445            return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
446        }
447
448        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
449            if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
450                return nft as &{ViewResolver.Resolver}
451            }
452            return nil
453        }
454
455        access(contract) fun refund(id: UInt64, k: UFix64) {
456            if let address = self.owner?.address {
457                let receiverRef = getAccount(address).capabilities.borrow<&{FungibleToken.Receiver}>(StarlyToken.TokenPublicReceiverPath)
458                    ?? panic("Could not borrow StarlyToken receiver reference to the recipient's vault!")
459                let stake <- self.withdraw(withdrawID: id) as! @StarlyTokenStaking.NFT
460                let burner = StarlyTokenStaking.account.storage.borrow<&NFTBurner>(from: StarlyTokenStaking.BurnerStoragePath)!
461                let unstakeVault <-burner.burnStake(
462                    stake: <-stake,
463                    k: k,
464                    address: address,
465                    minStakingSeconds: StarlyTokenStaking.minStakingSeconds,
466                    unstakingFee: StarlyTokenStaking.unstakingFee,
467                    unstakingFlatFee: StarlyTokenStaking.unstakingFlatFee,
468                    unstakingFeesNotAppliedAfterSeconds: StarlyTokenStaking.unstakingFeesNotAppliedAfterSeconds,
469                    unstakingDisabledUntilTimestamp: StarlyTokenStaking.unstakingDisabledUntilTimestamp)
470                receiverRef.deposit(from: <-unstakeVault)
471            }
472        }
473
474        access(all) fun borrowStakePublic(id: UInt64): &StarlyTokenStaking.NFT {
475            let stakeRef = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
476            let intermediateRef = stakeRef as! &StarlyTokenStaking.NFT
477            return intermediateRef as &StarlyTokenStaking.NFT
478        }
479
480        access(all) fun stake(principalVault: @StarlyToken.Vault) {
481            let minter = StarlyTokenStaking.account.storage.borrow<&NFTMinter>(from: StarlyTokenStaking.MinterStoragePath)!
482            let stake <- minter.mintStake(
483                address: self.owner?.address,
484                principalVault: <-principalVault,
485                stakeTimestamp: getCurrentBlock().timestamp,
486                minStakingSeconds: StarlyTokenStaking.minStakingSeconds,
487                k: StarlyTokenStaking.k)
488            self.deposit(token: <-stake)
489        }
490
491        access(all) fun unstake(id: UInt64): @StarlyToken.Vault {
492            let burner = StarlyTokenStaking.account.storage.borrow<&NFTBurner>(from: StarlyTokenStaking.BurnerStoragePath)!
493            let stake <- self.withdraw(withdrawID: id) as! @StarlyTokenStaking.NFT
494            let k = stake.k
495            return <-burner.burnStake(
496                stake: <-stake,
497                k: k,
498                address: self.owner?.address,
499                minStakingSeconds: StarlyTokenStaking.minStakingSeconds,
500                unstakingFee: StarlyTokenStaking.unstakingFee,
501                unstakingFlatFee: StarlyTokenStaking.unstakingFlatFee,
502                unstakingFeesNotAppliedAfterSeconds: StarlyTokenStaking.unstakingFeesNotAppliedAfterSeconds,
503                unstakingDisabledUntilTimestamp: StarlyTokenStaking.unstakingDisabledUntilTimestamp)
504        }
505
506        access(all) fun unstakeAll(): @StarlyToken.Vault {
507            let burner = StarlyTokenStaking.account.storage.borrow<&NFTBurner>(from: StarlyTokenStaking.BurnerStoragePath)
508            let unstakeVault <- StarlyToken.createEmptyVault(vaultType: Type<@StarlyToken.Vault>()) as! @StarlyToken.Vault
509            let stakeIDs = self.getIDs()
510            for stakeID in stakeIDs {
511                unstakeVault.deposit(from: <-self.unstake(id: stakeID))
512            }
513            return <-unstakeVault
514        }
515
516        access(all) fun borrowStakePrivate(id: UInt64): &StarlyTokenStaking.NFT {
517            let stakePassRef = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
518            return stakePassRef as! &StarlyTokenStaking.NFT
519        }
520    }
521
522    access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
523        return <- create Collection()
524    }
525
526    // Admin resource for controlling the configuration parameters and refunding
527    access(all) resource Admin {
528
529        access(all) fun setStakingEnabled(_ enabled: Bool) {
530            StarlyTokenStaking.stakingEnabled = enabled
531        }
532
533        access(all) fun setUnstakingEnabled(_ enabled: Bool) {
534            StarlyTokenStaking.unstakingEnabled = enabled
535        }
536
537        access(all) fun setUnstakingFee(_ amount: UFix64) {
538            StarlyTokenStaking.unstakingFee = amount
539        }
540
541        access(all) fun setUnstakingFlatFee(_ amount: UFix64) {
542            StarlyTokenStaking.unstakingFlatFee = amount
543        }
544
545        access(all) fun setUnstakingFeesNotAppliedAfterSeconds(_ seconds: UFix64) {
546            StarlyTokenStaking.unstakingFeesNotAppliedAfterSeconds = seconds
547        }
548
549        access(all) fun setMinStakingSeconds(_ seconds: UFix64) {
550            StarlyTokenStaking.minStakingSeconds = seconds
551        }
552
553        access(all) fun setMinStakePrincipal(_ amount: UFix64) {
554            StarlyTokenStaking.minStakePrincipal = amount
555        }
556
557        access(all) fun setUnstakingDisabledUntilTimestamp(_ timestamp: UFix64) {
558            StarlyTokenStaking.unstakingDisabledUntilTimestamp = timestamp
559        }
560
561        access(all) fun setK(_ k: UFix64) {
562            pre {
563                k <= CompoundInterest.k200: "Global K cannot be large larger than 200% APY"
564            }
565            StarlyTokenStaking.k = k
566        }
567
568        access(all) fun refund(collection: &{StarlyTokenStaking.CollectionPublic}, id: UInt64, k: UFix64) {
569            collection.refund(id: id, k: k)
570        }
571
572        access(all) fun createNFTMinter(): @NFTMinter {
573            return <-create NFTMinter()
574        }
575
576        access(all) fun createNFTBurner(): @NFTBurner {
577            return <-create NFTBurner()
578        }
579    }
580
581    access(all) view fun getContractViews(resourceType: Type?): [Type] {
582        return [
583            Type<MetadataViews.NFTCollectionData>()
584        ]
585    }
586
587    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
588        switch viewType {
589            case Type<MetadataViews.NFTCollectionData>():
590                return MetadataViews.NFTCollectionData(
591                    storagePath: StarlyTokenStaking.CollectionStoragePath,
592                    publicPath: StarlyTokenStaking.CollectionPublicPath,
593                    publicCollection: Type<&StarlyTokenStaking.Collection>(),
594                    publicLinkedType: Type<&StarlyTokenStaking.Collection>(),
595                    createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
596                        return <-StarlyTokenStaking.createEmptyCollection(nftType: Type<@StarlyTokenStaking.NFT>())
597                    }))
598        }
599        return nil
600    }
601
602    init() {
603        self.totalSupply = 0
604        self.totalPrincipalStaked = 0.0
605        self.totalInterestPaid = 0.0
606
607        self.stakingEnabled = true
608        self.unstakingEnabled = true
609        self.unstakingFee = 0.0
610        self.unstakingFlatFee = 0.0
611        self.unstakingFeesNotAppliedAfterSeconds = 0.0
612        self.minStakingSeconds = 0.0
613        self.minStakePrincipal = 0.0
614        self.unstakingDisabledUntilTimestamp = 0.0
615        self.k = CompoundInterest.k15 // 15% APY for Starly
616
617        self.CollectionStoragePath = /storage/starlyTokenStakingCollection
618        self.CollectionPublicPath = /public/starlyTokenStakingCollection
619        self.AdminStoragePath = /storage/starlyTokenStakingAdmin
620        self.MinterStoragePath = /storage/starlyTokenStakingMinter
621        self.BurnerStoragePath = /storage/starlyTokenStakingBurner
622
623        let admin <- create Admin()
624        let minter <- admin.createNFTMinter()
625        let burner <- admin.createNFTBurner()
626        self.account.storage.save(<-admin, to: self.AdminStoragePath)
627        self.account.storage.save(<-minter, to: self.MinterStoragePath)
628        self.account.storage.save(<-burner, to: self.BurnerStoragePath)
629
630        // for interests we will use account's default Starly token vault
631        if (self.account.storage.borrow<&StarlyToken.Vault>(from: StarlyToken.TokenStoragePath) == nil) {
632            let starlyTokenCap = self.account.capabilities.storage.issue<&StarlyToken.Vault>(StarlyToken.TokenStoragePath)
633            self.account.capabilities.publish(starlyTokenCap, at: StarlyToken.TokenPublicBalancePath)
634            let receiverCap = self.account.capabilities.storage.issue<&StarlyToken.Vault>(StarlyToken.TokenStoragePath)
635            self.account.capabilities.publish(receiverCap, at: StarlyToken.TokenPublicReceiverPath)
636            self.account.storage.save(<-StarlyToken.createEmptyVault(vaultType: Type<@StarlyToken.Vault>()), to: /storage/starlyTokenVault)
637        }
638
639        emit ContractInitialized()
640    }
641}
642