Smart Contract

LockedTokens

A.8d0e87b65159ae63.LockedTokens

Valid From

115,209,879

Deployed

3d ago
Feb 24, 2026, 08:57:24 AM UTC

Dependents

468 imports
1/*
2
3    LockedTokens implements the functionality required to manage FLOW
4    buyers locked tokens from the token sale.
5
6    Each token holder gets two accounts. One account is their locked token
7    account. It will be jointly controlled by the user and the token administrator.
8    The token administrator must co-sign the transfer of any locked tokens.
9    The token admin cannot interact with the account
10    without approval from the token holder,
11    except to deposit additional locked FLOW
12    or to unlock existing FLOW at each milestone in the token vesting period.
13
14    The second account is the unlocked user account. This account is
15    in full possesion and control of the user and they can do whatever
16    they want with it. This account will store a capability that allows
17    them to withdraw tokens when they become unlocked and also to
18    perform staking operations with their locked tokens.
19
20    When a user account is created, both accounts are initialized with
21    their respective objects: LockedTokenManager for the shared account,
22    and TokenHolder for the unlocked account. The user calls functions
23    on TokenHolder to withdraw tokens from the shared account and to
24    perform staking actions with the locked tokens
25
26 */
27
28import FlowToken from 0x1654653399040a61
29import FungibleToken from 0xf233dcee88fe0abe
30import FlowIDTableStaking from 0x8624b52f9ddcd04a
31import FlowStorageFees from 0xe467b9dd11fa00df
32import StakingProxy from 0x62430cf28c26d095
33
34access(all) contract LockedTokens {
35
36    access(all) event SharedAccountRegistered(address: Address)
37    access(all) event UnlockedAccountRegistered(address: Address)
38
39    access(all) event UnlockLimitIncreased(address: Address, increaseAmount: UFix64, newLimit: UFix64)
40
41    access(all) event LockedAccountRegisteredAsNode(address: Address, nodeID: String)
42    access(all) event LockedAccountRegisteredAsDelegator(address: Address, nodeID: String)
43
44    access(all) event LockedTokensDeposited(address: Address, amount: UFix64)
45
46    /// Path to store the locked token manager resource
47    /// in the shared account
48    access(all) let LockedTokenManagerStoragePath: StoragePath
49
50    /// Path to store the private capability for the token
51    /// manager
52    access(all) let LockedTokenManagerPrivatePath: PrivatePath
53
54    /// Path to store the private locked token admin link
55    /// in the shared account
56    access(all) let LockedTokenAdminPrivatePath: PrivatePath
57
58    /// Path to store the admin collection
59    /// in the admin account
60    access(all) let LockedTokenAdminCollectionStoragePath: StoragePath
61
62    /// Path to store the token holder resource
63    /// in the unlocked account
64    access(all) let TokenHolderStoragePath: StoragePath
65
66    /// Public path to store the capability that allows
67    /// reading information about a locked account
68    access(all) let LockedAccountInfoPublicPath: PublicPath
69
70    /// Path that an account creator would store
71    /// the resource that they use to create locked accounts
72    access(all) let LockedAccountCreatorStoragePath: StoragePath
73
74    /// Path that an account creator would publish
75    /// their capability for the token admin to
76    /// deposit the account creation capability
77    access(all) let LockedAccountCreatorPublicPath: PublicPath
78
79    /// The TokenAdmin capability allows the token administrator to unlock tokens at each
80    /// milestone in the vesting period.
81    access(all) resource interface TokenAdmin {
82        access(UnlockTokens) fun increaseUnlockLimit(delta: UFix64)
83    }
84
85    /// Entitlement for the token admin to unlock tokens from
86    /// the token sale and various grants
87    access(all) entitlement UnlockTokens
88
89    /// Entitlement for the token admin to use to recover leased tokens
90    /// directly from accounts who have leased tokens to operate nodes
91    /// Since there are no existing capabilities with this entitlement,
92    /// it is only used when authorizing a transaction that already
93    /// has access to the account
94    access(all) entitlement RecoverLease
95
96    /// This token manager resource is stored in the shared account to manage access
97    /// to the locked token vault and to the staking/delegating resources.
98    access(all) resource LockedTokenManager: FungibleToken.Receiver, FungibleToken.Provider, TokenAdmin {
99
100        /// This is a reference to the default FLOW vault stored in the shared account.
101        ///
102        /// All locked FLOW tokens are stored in this vault, which can be accessed in two ways:
103        ///   1) Directly, in a transaction co-signed by both the token holder and token administrator
104        ///   2) Indirectly via the LockedTokenManager, in a transaction signed by the token holder
105        access(account) var vault: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>
106
107        /// The amount of tokens that the user can withdraw.
108        /// It is decreased when the user withdraws
109        access(all) var unlockLimit: UFix64
110
111        /// Optional NodeStaker resource. Will only be filled if the user
112        /// signs up to be a node operator
113        access(contract) var nodeStaker: @FlowIDTableStaking.NodeStaker?
114
115        /// Optional NodeDelegator resource. Will only be filled if the user
116        /// signs up to be a delegator
117        access(contract) var nodeDelegator: @FlowIDTableStaking.NodeDelegator?
118
119        init(vault: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>) {
120            self.vault = vault
121            self.nodeStaker <- nil
122            self.nodeDelegator <- nil
123            self.unlockLimit = 0.0
124        }
125
126        // FungibleToken.Receiver actions
127        access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
128            return {Type<@FlowToken.Vault>(): true}
129        }
130
131        /// Returns whether or not the given type is accepted by the Receiver
132        /// A vault that can accept any type should just return true by default
133        access(all) view fun isSupportedVaultType(type: Type): Bool {
134            if let isSupported = self.getSupportedVaultTypes()[type] { 
135                return isSupported 
136            } else { return false }
137        }
138
139        /// Deposits unlocked tokens to the vault
140        access(all) fun deposit(from: @{FungibleToken.Vault}) {
141            self.depositUnlockedTokens(from: <-from)
142        }
143
144        access(self) fun depositUnlockedTokens(from: @{FungibleToken.Vault}) {
145            let vaultRef = self.vault.borrow()!
146
147            let balance = from.balance
148
149            vaultRef.deposit(from: <- from)
150
151            self.increaseUnlockLimit(delta: balance)
152        }
153
154        // FungibleToken.Provider actions
155
156        /// Asks if the amount can be withdrawn from this vault
157        access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
158            let vaultRef = self.vault.borrow()!
159            return amount <= vaultRef.balance && amount <= self.unlockLimit
160        }
161
162        /// Withdraws unlocked tokens from the vault
163        access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
164            return <-self.withdrawUnlockedTokens(amount: amount)
165        }
166
167        access(self) fun withdrawUnlockedTokens(amount: UFix64): @{FungibleToken.Vault} {
168            pre {
169                self.unlockLimit >= amount: "Requested amount exceeds unlocked token limit"
170            }
171
172            post {
173                self.unlockLimit == before(self.unlockLimit) - amount: "Updated unlocked token limit is incorrect"
174            }
175
176            let vaultRef = self.vault.borrow()!
177
178            let vault <- vaultRef.withdraw(amount: amount)
179
180            self.decreaseUnlockLimit(delta: amount)
181
182            return <-vault
183        }
184
185        access(all) view fun getBalance(): UFix64 {
186            let vaultRef = self.vault.borrow()!
187            return vaultRef.balance
188        }
189
190        access(self) fun decreaseUnlockLimit(delta: UFix64) {
191            self.unlockLimit = self.unlockLimit - delta
192        }
193
194        // LockedTokens.TokenAdmin actions
195
196        /// Called by the admin every time a vesting release happens
197        access(UnlockTokens) fun increaseUnlockLimit(delta: UFix64) {
198            self.unlockLimit = self.unlockLimit + delta
199            emit UnlockLimitIncreased(address: self.owner!.address, increaseAmount: delta, newLimit: self.unlockLimit)
200        }
201
202        // LockedTokens.TokenHolder actions
203
204        /// Registers a new node operator with the Flow Staking contract
205        /// and commits an initial amount of locked tokens to stake
206        access(account) fun registerNode(nodeInfo: StakingProxy.NodeInfo,
207                                         stakingKeyPoP: String,
208                                         amount: UFix64
209        ) {
210            if let nodeStaker <- self.nodeStaker <- nil {
211                let stakingInfo = FlowIDTableStaking.NodeInfo(nodeID: nodeStaker.id)
212
213                assert(
214                    stakingInfo.tokensStaked + stakingInfo.tokensCommitted + stakingInfo.tokensUnstaking + stakingInfo.tokensUnstaked + stakingInfo.tokensRewarded == 0.0,
215                    message: "Cannot register a new node until all tokens from the previous node have been withdrawn"
216                )
217
218                destroy nodeStaker
219            }
220
221            let vaultRef = self.vault.borrow()!
222
223            let tokens <- vaultRef.withdraw(amount: amount)
224
225            let nodeStaker <- self.nodeStaker <- FlowIDTableStaking.addNodeRecord(id: nodeInfo.id,
226                                                    role: nodeInfo.role,
227                                                    networkingAddress: nodeInfo.networkingAddress,
228                                                    networkingKey: nodeInfo.networkingKey,
229                                                    stakingKey: nodeInfo.stakingKey,
230                                                    stakingKeyPoP: stakingKeyPoP,
231                                                    tokensCommitted: <-tokens)
232
233            destroy nodeStaker
234
235            emit LockedAccountRegisteredAsNode(address: self.owner!.address, nodeID: nodeInfo.id)
236        }
237
238        /// Registers a new Delegator with the Flow Staking contract
239        /// the caller has to specify the ID of the node operator
240        /// they are delegating to
241        access(account) fun registerDelegator(nodeID: String, amount: UFix64) {
242            if let delegator <- self.nodeDelegator <- nil {
243                let delegatorInfo = FlowIDTableStaking.DelegatorInfo(nodeID: delegator.nodeID, delegatorID: delegator.id)
244
245                assert(
246                    delegatorInfo.tokensStaked + delegatorInfo.tokensCommitted + delegatorInfo.tokensUnstaking + delegatorInfo.tokensUnstaked + delegatorInfo.tokensRewarded == 0.0,
247                    message: "Cannot register a new delegator until all tokens from the previous node have been withdrawn"
248                )
249
250                destroy delegator
251            }
252
253            let vaultRef = self.vault.borrow()!
254
255            assert(
256                vaultRef.balance >= FlowIDTableStaking.getDelegatorMinimumStakeRequirement(),
257                message: "Must have the delegation minimum FLOW requirement in the locked vault to register a node"
258            )
259
260            let tokens <- vaultRef.withdraw(amount: amount)
261
262            let delegator <- self.nodeDelegator <- FlowIDTableStaking.registerNewDelegator(nodeID: nodeID, tokensCommitted: <-tokens)
263
264            destroy delegator
265
266            emit LockedAccountRegisteredAsDelegator(address: self.owner!.address, nodeID: nodeID)
267        }
268
269        access(account) view fun borrowNode(): auth(FlowIDTableStaking.NodeOperator) &FlowIDTableStaking.NodeStaker? {
270            let nodeRef = &self.nodeStaker as auth(FlowIDTableStaking.NodeOperator) &FlowIDTableStaking.NodeStaker?
271            return nodeRef
272        }
273
274        access(account) view fun borrowDelegator(): auth(FlowIDTableStaking.DelegatorOwner) &FlowIDTableStaking.NodeDelegator? {
275            let delegatorRef = &self.nodeDelegator as auth(FlowIDTableStaking.DelegatorOwner) &FlowIDTableStaking.NodeDelegator?
276            return delegatorRef
277        }
278
279        /// The following two functions are late additions to replicate functionality
280        /// that was lost with the Crescendo upgrade
281        /// They allow the account that owns the TokenManager to borrow a reference to
282        /// the node or delegator directly from its storage
283        /// This is only used by the Flow Foundation to recover leases that it has given to node operators
284        access(RecoverLease) view fun borrowNodeForLease(): auth(FlowIDTableStaking.NodeOperator) &FlowIDTableStaking.NodeStaker? {
285            return self.borrowNode()
286        }
287
288        access(RecoverLease) view fun borrowDelegatorForLease(): auth(FlowIDTableStaking.DelegatorOwner) &FlowIDTableStaking.NodeDelegator? {
289            return self.borrowDelegator()
290        }
291
292        access(UnlockTokens) fun removeNode(): @FlowIDTableStaking.NodeStaker? {
293            let node <- self.nodeStaker <- nil
294
295            return <-node
296        }
297
298        access(UnlockTokens) fun removeDelegator(): @FlowIDTableStaking.NodeDelegator? {
299            let del <- self.nodeDelegator <- nil
300
301            return <-del
302        }
303    }
304
305    access(all) entitlement TokenOperations
306
307    /// This interfaces allows anybody to read information about the locked account.
308    /// Kept for backwards compatibility
309    access(all) resource interface LockedAccountInfo {
310        access(all) fun getLockedAccountAddress(): Address
311        access(all) view fun getLockedAccountBalance(): UFix64
312        access(all) fun getUnlockLimit(): UFix64
313        access(all) view fun getNodeID(): String?
314        access(all) view fun getDelegatorID(): UInt32?
315        access(all) view fun getDelegatorNodeID(): String?
316    }
317
318    /// Stored in Holder unlocked account
319    access(all) resource TokenHolder: FungibleToken.Receiver, FungibleToken.Provider, LockedAccountInfo {
320
321        /// The address of the shared (locked) account.
322        access(all) var address: Address
323
324        /// Capability that is used to access the LockedTokenManager
325        /// in the shared account
326        access(account) var tokenManager: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>
327
328        /// Used to perform staking actions if the user has signed up
329        /// as a node operator
330        access(self) var nodeStakerProxy: LockedNodeStakerProxy?
331
332        /// Used to perform delegating actions if the user has signed up
333        /// as a delegator
334        access(self) var nodeDelegatorProxy: LockedNodeDelegatorProxy?
335
336        init(lockedAddress: Address, tokenManager: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>) {
337            pre {
338                tokenManager.borrow() != nil: "Must pass a LockedTokenManager capability"
339            }
340
341            self.address = lockedAddress
342            self.tokenManager = tokenManager
343
344            // Create a new staker proxy that can be accessed in transactions
345            self.nodeStakerProxy = LockedNodeStakerProxy(tokenManager: self.tokenManager)
346
347            // create a new delegator proxy that can be accessed in transactions
348            self.nodeDelegatorProxy = LockedNodeDelegatorProxy(tokenManager: self.tokenManager)
349        }
350
351        /// Utility function to borrow a reference to the LockedTokenManager object
352        access(account) view fun borrowTokenManager(): auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager {
353            return self.tokenManager.borrow()!
354        }
355
356        /// Returns the locked account address for this token holder.
357        access(all) view fun getLockedAccountAddress(): Address {
358            return self.address
359        }
360
361        /// Returns the locked account balance for this token holder.
362        /// Subtracts the minimum storage reservation from the value because that portion
363        /// of the locked balance is not available to use
364        access(all) view fun getLockedAccountBalance(): UFix64 {
365
366            let balance = self.borrowTokenManager().getBalance()
367
368            if balance < FlowStorageFees.minimumStorageReservation {
369                return 0.0
370            }
371            return balance - FlowStorageFees.minimumStorageReservation
372        }
373
374        // Returns the unlocked limit for this token holder.
375        access(all) view fun getUnlockLimit(): UFix64 {
376            return self.borrowTokenManager().unlockLimit
377        }
378
379        access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
380            return {Type<@FlowToken.Vault>(): true}
381        }
382
383        /// Returns whether or not the given type is accepted by the Receiver
384        /// A vault that can accept any type should just return true by default
385        access(all) view fun isSupportedVaultType(type: Type): Bool {
386            if let isSupported = self.getSupportedVaultTypes()[type] { 
387                return isSupported 
388            } else { return false }
389        }
390
391        /// Deposits tokens in the locked vault, which marks them as
392        /// unlocked and available to withdraw
393        access(all) fun deposit(from: @{FungibleToken.Vault}) {
394            self.borrowTokenManager().deposit(from: <-from)
395        }
396
397        // FungibleToken.Provider actions
398
399        /// Asks if the amount can be withdrawn from this vault
400        access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
401            return amount <= self.getLockedAccountBalance()
402        }
403
404        /// Withdraws tokens from the locked vault. This will only succeed
405        /// if the withdraw amount is less than or equal to the limit
406        access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
407            return <- self.borrowTokenManager().withdraw(amount: amount)
408        }
409
410        /// The user calls this function if they want to register as a node operator
411        /// They have to provide all the info for their node
412        access(TokenOperations) fun createNodeStaker(nodeInfo: StakingProxy.NodeInfo, stakingKeyPoP: String, amount: UFix64) {
413
414            self.borrowTokenManager().registerNode(nodeInfo: nodeInfo, stakingKeyPoP: stakingKeyPoP, amount: amount)
415
416            // Create a new staker proxy that can be accessed in transactions
417            self.nodeStakerProxy = LockedNodeStakerProxy(tokenManager: self.tokenManager)
418        }
419
420        /// The user calls this function if they want to register as a node operator
421        /// They have to provide the node ID for the node they want to delegate to
422        access(TokenOperations) fun createNodeDelegator(nodeID: String) {
423
424            self.borrowTokenManager().registerDelegator(nodeID: nodeID, amount: FlowIDTableStaking.getDelegatorMinimumStakeRequirement())
425
426            // create a new delegator proxy that can be accessed in transactions
427            self.nodeDelegatorProxy = LockedNodeDelegatorProxy(tokenManager: self.tokenManager)
428        }
429
430        /// Borrow a "reference" to the staking object which allows the caller
431        /// to perform all staking actions with locked tokens.
432        access(TokenOperations) fun borrowStaker(): LockedNodeStakerProxy {
433            pre {
434                self.nodeStakerProxy != nil:
435                    "The NodeStakerProxy doesn't exist!"
436            }
437            return self.nodeStakerProxy!
438        }
439
440        access(all) view fun getNodeID(): String? {
441            let tokenManager = self.tokenManager.borrow()!
442
443            return tokenManager.nodeStaker?.id
444        }
445
446        /// Borrow a "reference" to the delegating object which allows the caller
447        /// to perform all delegating actions with locked tokens.
448        access(TokenOperations) fun borrowDelegator(): LockedNodeDelegatorProxy {
449            pre {
450                self.nodeDelegatorProxy != nil:
451                    "The NodeDelegatorProxy doesn't exist!"
452            }
453            return self.nodeDelegatorProxy!
454        }
455
456        access(all) view fun getDelegatorID(): UInt32? {
457            let tokenManager = self.tokenManager.borrow()!
458
459            return tokenManager.nodeDelegator?.id
460        }
461
462        access(all) view fun getDelegatorNodeID(): String? {
463            let tokenManager = self.tokenManager.borrow()!
464
465            return tokenManager.nodeDelegator?.nodeID
466        }
467
468    }
469
470    /// Used to perform staking actions
471    access(all) struct LockedNodeStakerProxy: StakingProxy.NodeStakerProxy {
472
473        access(self) var tokenManager: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>
474
475        init(tokenManager: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>) {
476            pre {
477                tokenManager.borrow() != nil: "Invalid token manager capability"
478            }
479            self.tokenManager = tokenManager
480        }
481
482        access(self) fun nodeObjectExists(_ managerRef: &LockedTokenManager): Bool {
483            return managerRef.nodeStaker != nil
484        }
485
486        /// Change node networking address
487        access(all) fun updateNetworkingAddress(_ newAddress: String) {
488            let tokenManagerRef = self.tokenManager.borrow()!
489
490            assert(
491                self.nodeObjectExists(tokenManagerRef),
492                message: "Cannot change networking address if there is no node object!"
493            )
494
495            tokenManagerRef.borrowNode()?.updateNetworkingAddress(newAddress)
496        }
497
498        /// Stakes new locked tokens
499        access(all) fun stakeNewTokens(amount: UFix64) {
500            let tokenManagerRef = self.tokenManager.borrow()!
501
502            assert(
503                self.nodeObjectExists(tokenManagerRef),
504                message: "Cannot stake if there is no node object!"
505            )
506
507            let vaultRef = tokenManagerRef.vault.borrow()!
508
509            tokenManagerRef.borrowNode()?.stakeNewTokens(<-vaultRef.withdraw(amount: amount))
510        }
511
512        /// Stakes unstaked tokens from the staking contract
513        access(all) fun stakeUnstakedTokens(amount: UFix64) {
514            let tokenManagerRef = self.tokenManager.borrow()!
515
516            assert(
517                self.nodeObjectExists(tokenManagerRef),
518                message: "Cannot stake if there is no node object!"
519            )
520
521            tokenManagerRef.borrowNode()?.stakeUnstakedTokens(amount: amount)
522        }
523
524        /// Stakes rewarded tokens. Rewarded tokens are freely withdrawable
525        /// so if they are staked, the withdraw limit should be increased
526        /// because staked tokens are effectively treated as locked tokens
527        access(all) fun stakeRewardedTokens(amount: UFix64) {
528            let tokenManagerRef = self.tokenManager.borrow()!
529
530            assert(
531                self.nodeObjectExists(tokenManagerRef),
532                message: "Cannot stake if there is no node object!"
533            )
534
535            tokenManagerRef.borrowNode()?.stakeRewardedTokens(amount: amount)
536
537            tokenManagerRef.increaseUnlockLimit(delta: amount)
538        }
539
540        /// Requests unstaking for the node
541        access(all) fun requestUnstaking(amount: UFix64) {
542            let tokenManagerRef = self.tokenManager.borrow()!
543
544            assert(
545                self.nodeObjectExists(tokenManagerRef),
546                message: "Cannot stake if there is no node object!"
547            )
548
549            tokenManagerRef.borrowNode()?.requestUnstaking(amount: amount)
550        }
551
552        /// Requests to unstake all of the node's tokens and all of
553        /// the tokens that have been delegated to the node
554        access(all) fun unstakeAll() {
555            let tokenManagerRef = self.tokenManager.borrow()!
556
557            assert(
558                self.nodeObjectExists(tokenManagerRef),
559                message: "Cannot stake if there is no node object!"
560            )
561
562            tokenManagerRef.borrowNode()?.unstakeAll()
563        }
564
565        /// Withdraw the unstaked tokens back to
566        /// the locked token vault. This does not increase the withdraw
567        /// limit because staked/unstaked tokens are considered to still
568        /// be locked in terms of the vesting schedule
569        access(all) fun withdrawUnstakedTokens(amount: UFix64) {
570            let tokenManagerRef = self.tokenManager.borrow()!
571
572            assert(
573                self.nodeObjectExists(tokenManagerRef),
574                message: "Cannot stake if there is no node object!"
575            )
576
577            let vaultRef = tokenManagerRef.vault.borrow()!
578
579            let withdrawnTokens <- tokenManagerRef.borrowNode()?.withdrawUnstakedTokens(amount: amount)!
580
581            vaultRef.deposit(from: <-withdrawnTokens)
582        }
583
584        /// Withdraw reward tokens to the locked vault,
585        /// which increases the withdraw limit
586        access(all) fun withdrawRewardedTokens(amount: UFix64) {
587            let tokenManagerRef = self.tokenManager.borrow()!
588
589            assert(
590                self.nodeObjectExists(tokenManagerRef),
591                message: "Cannot stake if there is no node object!"
592            )
593
594            tokenManagerRef.deposit(from: <-tokenManagerRef.borrowNode()?.withdrawRewardedTokens(amount: amount)!)
595        }
596    }
597
598    /// Used to perform delegating actions in transactions
599    access(all) struct LockedNodeDelegatorProxy: StakingProxy.NodeDelegatorProxy {
600
601        access(self) var tokenManager: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>
602
603        init(tokenManager: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>) {
604            pre {
605                tokenManager.borrow() != nil: "Invalid LockedTokenManager capability"
606            }
607            self.tokenManager = tokenManager
608        }
609
610        access(self) fun delegatorObjectExists(_ managerRef: &LockedTokenManager): Bool {
611            return managerRef.nodeDelegator != nil
612        }
613
614        /// delegates tokens from the locked token vault
615        access(all) fun delegateNewTokens(amount: UFix64) {
616            let tokenManagerRef = self.tokenManager.borrow()!
617
618            assert(
619                self.delegatorObjectExists(tokenManagerRef),
620                message: "Cannot stake if there is no delegator object!"
621            )
622
623            let vaultRef = tokenManagerRef.vault.borrow()!
624
625            tokenManagerRef.borrowDelegator()?.delegateNewTokens(from: <-vaultRef.withdraw(amount: amount))
626        }
627
628        /// Delegate tokens from the unstaked staking bucket
629        access(all) fun delegateUnstakedTokens(amount: UFix64) {
630            let tokenManagerRef = self.tokenManager.borrow()!
631
632            assert(
633                self.delegatorObjectExists(tokenManagerRef),
634                message: "Cannot stake if there is no delegator object!"
635            )
636
637            tokenManagerRef.borrowDelegator()?.delegateUnstakedTokens(amount: amount)
638        }
639
640        /// Delegate rewarded tokens. Increases the unlock limit
641        /// because these are freely withdrawable
642        access(all) fun delegateRewardedTokens(amount: UFix64) {
643            let tokenManagerRef = self.tokenManager.borrow()!
644
645            assert(
646                self.delegatorObjectExists(tokenManagerRef),
647                message: "Cannot stake if there is no delegator object!"
648            )
649
650            tokenManagerRef.borrowDelegator()?.delegateRewardedTokens(amount: amount)
651
652            tokenManagerRef.increaseUnlockLimit(delta: amount)
653        }
654
655        /// Request to unstake tokens
656        access(all) fun requestUnstaking(amount: UFix64) {
657            let tokenManagerRef = self.tokenManager.borrow()!
658
659            assert(
660                self.delegatorObjectExists(tokenManagerRef),
661                message: "Cannot stake if there is no delegator object!"
662            )
663
664            tokenManagerRef.borrowDelegator()?.requestUnstaking(amount: amount)
665        }
666
667        /// withdraw unstaked tokens back to the locked vault
668        /// This does not increase the withdraw limit
669        access(all) fun withdrawUnstakedTokens(amount: UFix64) {
670            let tokenManagerRef = self.tokenManager.borrow()!
671
672            assert(
673                self.delegatorObjectExists(tokenManagerRef),
674                message: "Cannot stake if there is no delegator object!"
675            )
676
677            let vaultRef = tokenManagerRef.vault.borrow()!
678
679            vaultRef.deposit(from: <-tokenManagerRef.borrowDelegator()?.withdrawUnstakedTokens(amount: amount)!)
680        }
681
682        /// Withdraw rewarded tokens back to the locked vault,
683        /// which increases the withdraw limit because these
684        /// are considered unstaked in terms of the vesting schedule
685        access(all) fun withdrawRewardedTokens(amount: UFix64) {
686            let tokenManagerRef = self.tokenManager.borrow()!
687
688            assert(
689                self.delegatorObjectExists(tokenManagerRef),
690                message: "Cannot stake if there is no delegator object!"
691            )
692
693            tokenManagerRef.deposit(from: <-tokenManagerRef.borrowDelegator()?.withdrawRewardedTokens(amount: amount)!)
694        }
695    }
696
697    access(all) entitlement AccountCreator
698
699    access(all) resource interface AddAccount {
700        access(AccountCreator) fun addAccount(
701            sharedAccountAddress: Address,
702            unlockedAccountAddress: Address,
703            tokenAdmin: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>)
704    }
705
706    /// Resource that the Flow token admin
707    /// stores in their account to manage the vesting schedule
708    /// for all the token holders
709    access(all) resource TokenAdminCollection: AddAccount {
710
711        /// Mapping of account addresses to LockedTokenManager capabilities
712        access(self) var accounts: {Address: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>}
713
714        init() {
715            self.accounts = {}
716        }
717
718        /// Add a new account's locked token manager capability
719        /// to the record
720        access(AccountCreator) fun addAccount(
721            sharedAccountAddress: Address,
722            unlockedAccountAddress: Address,
723            tokenAdmin: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>)
724        {
725            self.accounts[sharedAccountAddress] = tokenAdmin
726            emit SharedAccountRegistered(address: sharedAccountAddress)
727            emit UnlockedAccountRegistered(address: unlockedAccountAddress)
728        }
729
730        /// Get an accounts capability
731        access(all) fun getAccount(address: Address): Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>? {
732            return self.accounts[address]
733        }
734
735        access(all) fun createAdminCollection(): @TokenAdminCollection {
736            return <-create TokenAdminCollection()
737        }
738    }
739
740    access(all) resource interface LockedAccountCreatorPublic {
741        access(all) fun addCapability(cap: Capability<auth(LockedTokens.AccountCreator) &TokenAdminCollection>)
742    }
743
744    // account creators store this resource in their account
745    // in order to be able to register accounts who have locked tokens
746    access(all) resource LockedAccountCreator: LockedAccountCreatorPublic, AddAccount {
747
748        access(self) var addAccountCapability: Capability<auth(LockedTokens.AccountCreator) &TokenAdminCollection>?
749
750        init() {
751            self.addAccountCapability = nil
752        }
753
754        access(all) fun addCapability(cap: Capability<auth(LockedTokens.AccountCreator) &TokenAdminCollection>) {
755            pre {
756                cap.borrow() != nil: "Invalid token admin collection capability"
757            }
758            self.addAccountCapability = cap
759        }
760
761        access(AccountCreator) fun addAccount(sharedAccountAddress: Address,
762                           unlockedAccountAddress: Address,
763                           tokenAdmin: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>) {
764
765            pre {
766                self.addAccountCapability != nil:
767                    "Cannot add account until the token admin has deposited the account registration capability"
768                tokenAdmin.borrow() != nil:
769                    "Invalid tokenAdmin capability"
770            }
771
772            let adminRef = self.addAccountCapability!.borrow()!
773
774            adminRef.addAccount(sharedAccountAddress: sharedAccountAddress,
775                           unlockedAccountAddress: unlockedAccountAddress,
776                           tokenAdmin: tokenAdmin)
777        }
778    }
779
780    /// Public function to create a new Locked Token Manager
781    /// every time a new user account is created
782    access(all) fun createLockedTokenManager(vault: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>): @LockedTokenManager {
783        return <- create LockedTokenManager(vault: vault)
784    }
785
786    // Creates a new TokenHolder resource for this LockedTokenManager
787    /// that the user can store in their unlocked account.
788    access(all) fun createTokenHolder(lockedAddress: Address, tokenManager: Capability<auth(FungibleToken.Withdraw, LockedTokens.UnlockTokens) &LockedTokenManager>): @TokenHolder {
789        return <- create TokenHolder(lockedAddress: lockedAddress, tokenManager: tokenManager)
790    }
791
792    access(all) fun createLockedAccountCreator(): @LockedAccountCreator {
793        return <-create LockedAccountCreator()
794    }
795
796    init(admin: auth(Storage) &Account) {
797        self.LockedTokenManagerStoragePath = /storage/lockedTokenManager
798        self.LockedTokenManagerPrivatePath = /private/lockedTokenManager
799
800        self.LockedTokenAdminPrivatePath = /private/lockedTokenAdmin
801        self.LockedTokenAdminCollectionStoragePath = /storage/lockedTokenAdminCollection
802
803        self.TokenHolderStoragePath = /storage/flowTokenHolder
804        self.LockedAccountInfoPublicPath = /public/lockedAccountInfo
805
806        self.LockedAccountCreatorStoragePath = /storage/lockedAccountCreator
807        self.LockedAccountCreatorPublicPath = /public/lockedAccountCreator
808
809        /// create a single admin collection and store it
810        admin.storage.save(<-create TokenAdminCollection(), to: self.LockedTokenAdminCollectionStoragePath)
811    }
812}
813