Smart Contract
StarlyTokenStaking
A.76a9b420a331b9f0.StarlyTokenStaking
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