Smart Contract

VaultCore

A.79f5b5b0f95a160b.VaultCore

Valid From

128,833,673

Deployed

1w ago
Feb 20, 2026, 08:41:12 AM UTC

Dependents

20 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import stFlowToken from 0xd6f80565193ad727
4
5/// Core vault contract for non-custodial yield aggregation
6/// Users deposit funds, only they can withdraw their principal
7/// Agent manages funds across strategies to maximize APY
8access(all) contract VaultCore {
9    
10    // ====================================================================
11    // PATHS
12    // ====================================================================
13    access(all) let VaultStoragePath: StoragePath
14    access(all) let VaultPublicPath: PublicPath
15    access(all) let AdminStoragePath: StoragePath
16    access(all) let AgentStoragePath: StoragePath
17    
18    // ====================================================================
19    // EVENTS
20    // ====================================================================
21    access(all) event VaultInitialized()
22    access(all) event UserDeposited(user: Address, asset: String, amount: UFix64, shares: UFix64, riskLevel: UInt8)
23    access(all) event WithdrawalRequested(user: Address, asset: String, amount: UFix64, requestId: UInt64)
24    access(all) event WithdrawalProcessed(user: Address, requestId: UInt64, amount: UFix64)
25    access(all) event StrategyExecuted(strategy: String, asset: String, amount: UFix64)
26    access(all) event YieldHarvested(asset: String, amount: UFix64, fees: UFix64)
27    access(all) event BridgedToEVM(asset: String, amount: UFix64)
28    access(all) event BridgedFromEVM(asset: String, amount: UFix64)
29    access(all) event EmergencyModeToggled(enabled: Bool)
30    access(all) event YieldEligibilityChanged(user: Address, eligible: Bool)
31    
32    // ====================================================================
33    // STATE VARIABLES
34    // ====================================================================
35    access(self) var totalValueLocked: UFix64
36    access(self) var totalUsers: UInt64
37    access(self) var totalPrincipal: UFix64
38    access(self) var totalYieldGenerated: UFix64
39    access(self) var totalShares: UFix64
40    access(self) var currentEpoch: UInt64
41    access(self) var lastEpochStart: UFix64
42    access(self) var epochDuration: UFix64
43    
44    access(self) var depositsEnabled: Bool
45    access(self) var withdrawalsEnabled: Bool
46    access(self) var emergencyMode: Bool
47    
48    access(self) var totalBridgedToEVM: UFix64
49    access(self) var totalBridgedFromEVM: UFix64
50    
51    access(self) var nextRequestId: UInt64
52    
53    // ====================================================================
54    // ENUMS
55    // ====================================================================
56    access(all) enum RiskLevel: UInt8 {
57        access(all) case conservative
58        access(all) case normal
59        access(all) case aggressive
60    }
61    
62    access(all) enum AssetType: UInt8 {
63        access(all) case flow
64        access(all) case stflow
65        access(all) case usdc
66    }
67    
68    // ====================================================================
69    // STRUCTS
70    // ====================================================================
71    access(all) struct UserPosition {
72        access(all) let user: Address
73        access(all) var totalShares: UFix64
74        access(all) var flowDeposited: UFix64
75        access(all) var stFlowDeposited: UFix64
76        access(all) var lastDepositTime: UFix64
77        access(all) var riskLevel: RiskLevel
78        access(all) var yieldEligible: Bool
79        access(all) var vrfMultiplier: UFix64
80        access(all) var withdrawalRequests: [UInt64]
81        
82        init(user: Address, riskLevel: RiskLevel) {
83            self.user = user
84            self.totalShares = 0.0
85            self.flowDeposited = 0.0
86            self.stFlowDeposited = 0.0
87            self.lastDepositTime = getCurrentBlock().timestamp
88            self.riskLevel = riskLevel
89            self.yieldEligible = true
90            self.vrfMultiplier = 1.0
91            self.withdrawalRequests = []
92        }
93        
94        access(contract) fun addShares(amount: UFix64) {
95            self.totalShares = self.totalShares + amount
96        }
97        
98        access(contract) fun removeShares(amount: UFix64) {
99            self.totalShares = self.totalShares - amount
100        }
101        
102        access(contract) fun addFlowDeposit(amount: UFix64) {
103            self.flowDeposited = self.flowDeposited + amount
104            self.lastDepositTime = getCurrentBlock().timestamp
105        }
106        
107        access(contract) fun addStFlowDeposit(amount: UFix64) {
108            self.stFlowDeposited = self.stFlowDeposited + amount
109            self.lastDepositTime = getCurrentBlock().timestamp
110        }
111        
112        access(contract) fun removeFlowDeposit(amount: UFix64) {
113            self.flowDeposited = self.flowDeposited - amount
114        }
115        
116        access(contract) fun removeStFlowDeposit(amount: UFix64) {
117            self.stFlowDeposited = self.stFlowDeposited - amount
118        }
119        
120        access(contract) fun setYieldEligible(eligible: Bool) {
121            self.yieldEligible = eligible
122        }
123        
124        access(contract) fun setVRFMultiplier(multiplier: UFix64) {
125            self.vrfMultiplier = multiplier
126        }
127        
128        access(contract) fun addWithdrawalRequest(requestId: UInt64) {
129            self.withdrawalRequests.append(requestId)
130        }
131    }
132    
133    access(all) struct WithdrawalRequest {
134        access(all) let requestId: UInt64
135        access(all) let user: Address
136        access(all) let assetType: AssetType
137        access(all) let amount: UFix64
138        access(all) let requestTime: UFix64
139        access(all) var processed: Bool
140        
141        init(requestId: UInt64, user: Address, assetType: AssetType, amount: UFix64) {
142            self.requestId = requestId
143            self.user = user
144            self.assetType = assetType
145            self.amount = amount
146            self.requestTime = getCurrentBlock().timestamp
147            self.processed = false
148        }
149        
150        access(contract) fun markProcessed() {
151            self.processed = true
152        }
153    }
154    
155    access(all) struct AssetBalance {
156        access(all) var vaultBalance: UFix64
157        access(all) var strategyBalance: UFix64
158        access(all) var evmBalance: UFix64
159        access(all) var totalBalance: UFix64
160        access(all) var totalHarvested: UFix64
161        access(all) var lastHarvestTime: UFix64
162        
163        init() {
164            self.vaultBalance = 0.0
165            self.strategyBalance = 0.0
166            self.evmBalance = 0.0
167            self.totalBalance = 0.0
168            self.totalHarvested = 0.0
169            self.lastHarvestTime = getCurrentBlock().timestamp
170        }
171        
172        access(contract) fun addToVault(amount: UFix64) {
173            self.vaultBalance = self.vaultBalance + amount
174            self.totalBalance = self.totalBalance + amount
175        }
176        
177        access(contract) fun removeFromVault(amount: UFix64) {
178            self.vaultBalance = self.vaultBalance - amount
179            self.totalBalance = self.totalBalance - amount
180        }
181        
182        access(contract) fun moveToStrategy(amount: UFix64) {
183            self.vaultBalance = self.vaultBalance - amount
184            self.strategyBalance = self.strategyBalance + amount
185        }
186        
187        access(contract) fun moveFromStrategy(amount: UFix64) {
188            self.strategyBalance = self.strategyBalance - amount
189            self.vaultBalance = self.vaultBalance + amount
190        }
191        
192        access(contract) fun moveToEVM(amount: UFix64) {
193            self.vaultBalance = self.vaultBalance - amount
194            self.evmBalance = self.evmBalance + amount
195        }
196        
197        access(contract) fun moveFromEVM(amount: UFix64) {
198            self.evmBalance = self.evmBalance - amount
199            self.vaultBalance = self.vaultBalance + amount
200        }
201        
202        access(contract) fun recordHarvest(amount: UFix64) {
203            self.totalHarvested = self.totalHarvested + amount
204            self.lastHarvestTime = getCurrentBlock().timestamp
205        }
206    }
207    
208    access(all) struct VaultMetrics {
209        access(all) let totalValueLocked: UFix64
210        access(all) let totalUsers: UInt64
211        access(all) let totalShares: UFix64
212        access(all) let totalPrincipal: UFix64
213        access(all) let totalYieldGenerated: UFix64
214        access(all) let totalBridgedToEVM: UFix64
215        access(all) let totalBridgedFromEVM: UFix64
216        access(all) let currentEpoch: UInt64
217        access(all) let depositsEnabled: Bool
218        access(all) let withdrawalsEnabled: Bool
219        access(all) let emergencyMode: Bool
220        
221        init(
222            tvl: UFix64,
223            users: UInt64,
224            shares: UFix64,
225            principal: UFix64,
226            yieldGen: UFix64,
227            bridgedTo: UFix64,
228            bridgedFrom: UFix64,
229            epoch: UInt64,
230            deposits: Bool,
231            withdrawals: Bool,
232            emergency: Bool
233        ) {
234            self.totalValueLocked = tvl
235            self.totalUsers = users
236            self.totalShares = shares
237            self.totalPrincipal = principal
238            self.totalYieldGenerated = yieldGen
239            self.totalBridgedToEVM = bridgedTo
240            self.totalBridgedFromEVM = bridgedFrom
241            self.currentEpoch = epoch
242            self.depositsEnabled = deposits
243            self.withdrawalsEnabled = withdrawals
244            self.emergencyMode = emergency
245        }
246    }
247    
248    // ====================================================================
249    // VAULT RESOURCE
250    // ====================================================================
251    access(all) resource Vault {
252        // Asset storage
253        access(self) let flowVault: @FlowToken.Vault
254        access(self) let stFlowVault: @stFlowToken.Vault
255        
256        // User tracking
257        access(self) let userPositions: {Address: UserPosition}
258        access(self) let withdrawalRequests: {UInt64: WithdrawalRequest}
259        
260        // Asset balances
261        access(self) let assetBalances: {AssetType: AssetBalance}
262        
263        // Strategy tracking
264        access(self) let whitelistedStrategies: {String: Bool}
265        
266        init() {
267            self.flowVault <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) as! @FlowToken.Vault
268            self.stFlowVault <- stFlowToken.createEmptyVault(vaultType: Type<@stFlowToken.Vault>()) as! @stFlowToken.Vault
269            
270            self.userPositions = {}
271            self.withdrawalRequests = {}
272            
273            self.assetBalances = {}
274            self.assetBalances[AssetType.flow] = AssetBalance()
275            self.assetBalances[AssetType.stflow] = AssetBalance()
276            
277            self.whitelistedStrategies = {}
278        }
279        
280        // ====================================================================
281        // DEPOSIT FUNCTIONS
282        // ====================================================================
283        access(all) fun depositFlow(from: @FlowToken.Vault, user: Address, riskLevel: RiskLevel): UFix64 {
284            pre {
285                VaultCore.depositsEnabled: "Deposits are disabled"
286                !VaultCore.emergencyMode: "Emergency mode active"
287                from.balance > 0.0: "Cannot deposit zero"
288            }
289            
290            let amount = from.balance
291            let shares = self.calculateShares(assetType: AssetType.flow, amount: amount)
292            
293            // Deposit funds
294            self.flowVault.deposit(from: <-from)
295            self.assetBalances[AssetType.flow]!.addToVault(amount: amount)
296            
297            // Update or create user position
298            if self.userPositions[user] == nil {
299                self.userPositions[user] = UserPosition(user: user, riskLevel: riskLevel)
300                VaultCore.totalUsers = VaultCore.totalUsers + 1
301            }
302            
303            self.userPositions[user]!.addShares(amount: shares)
304            self.userPositions[user]!.addFlowDeposit(amount: amount)
305            
306            VaultCore.totalShares = VaultCore.totalShares + shares
307            VaultCore.totalPrincipal = VaultCore.totalPrincipal + amount
308            VaultCore.totalValueLocked = VaultCore.totalValueLocked + amount
309            
310            emit UserDeposited(
311                user: user,
312                asset: "FLOW",
313                amount: amount,
314                shares: shares,
315                riskLevel: riskLevel.rawValue
316            )
317            
318            return shares
319        }
320        
321        access(all) fun depositStFlow(from: @stFlowToken.Vault, user: Address, riskLevel: RiskLevel): UFix64 {
322            pre {
323                VaultCore.depositsEnabled: "Deposits are disabled"
324                !VaultCore.emergencyMode: "Emergency mode active"
325                from.balance > 0.0: "Cannot deposit zero"
326            }
327            
328            let amount = from.balance
329            let shares = self.calculateShares(assetType: AssetType.stflow, amount: amount)
330            
331            self.stFlowVault.deposit(from: <-from)
332            self.assetBalances[AssetType.stflow]!.addToVault(amount: amount)
333            
334            if self.userPositions[user] == nil {
335                self.userPositions[user] = UserPosition(user: user, riskLevel: riskLevel)
336                VaultCore.totalUsers = VaultCore.totalUsers + 1
337            }
338            
339            self.userPositions[user]!.addShares(amount: shares)
340            self.userPositions[user]!.addStFlowDeposit(amount: amount)
341            
342            VaultCore.totalShares = VaultCore.totalShares + shares
343            VaultCore.totalPrincipal = VaultCore.totalPrincipal + amount
344            VaultCore.totalValueLocked = VaultCore.totalValueLocked + amount
345            
346            emit UserDeposited(
347                user: user,
348                asset: "stFLOW",
349                amount: amount,
350                shares: shares,
351                riskLevel: riskLevel.rawValue
352            )
353            
354            return shares
355        }
356        
357        // ====================================================================
358        // WITHDRAWAL FUNCTIONS
359        // ====================================================================
360        access(all) fun requestWithdrawal(user: Address, assetType: AssetType, amount: UFix64): UInt64 {
361            pre {
362                self.userPositions[user] != nil: "No position found"
363                amount > 0.0: "Amount must be positive"
364            }
365            
366            let position = self.userPositions[user]!
367            
368            // Verify user has sufficient balance
369            if assetType == AssetType.flow {
370                assert(position.flowDeposited >= amount, message: "Insufficient FLOW balance")
371            } else if assetType == AssetType.stflow {
372                assert(position.stFlowDeposited >= amount, message: "Insufficient stFLOW balance")
373            }
374            
375            let requestId = VaultCore.nextRequestId
376            VaultCore.nextRequestId = requestId + 1
377            
378            let request = WithdrawalRequest(
379                requestId: requestId,
380                user: user,
381                assetType: assetType,
382                amount: amount
383            )
384            
385            self.withdrawalRequests[requestId] = request
386            self.userPositions[user]!.addWithdrawalRequest(requestId: requestId)
387            
388            emit WithdrawalRequested(
389                user: user,
390                asset: assetType == AssetType.flow ? "FLOW" : "stFLOW",
391                amount: amount,
392                requestId: requestId
393            )
394            
395            return requestId
396        }
397        
398        access(all) fun processWithdrawal(requestId: UInt64): @{FungibleToken.Vault} {
399            pre {
400                VaultCore.withdrawalsEnabled: "Withdrawals disabled"
401                self.withdrawalRequests[requestId] != nil: "Invalid request"
402                !self.withdrawalRequests[requestId]!.processed: "Already processed"
403            }
404            
405            let request = self.withdrawalRequests[requestId]!
406            let user = request.user
407            let amount = request.amount
408            let assetType = request.assetType
409            
410            // Mark as processed
411            self.withdrawalRequests[requestId]!.markProcessed()
412            
413            // Calculate shares to burn
414            let shares = self.calculateShares(assetType: assetType, amount: amount)
415            
416            // Update user position
417            if assetType == AssetType.flow {
418                self.userPositions[user]!.removeFlowDeposit(amount: amount)
419                self.assetBalances[AssetType.flow]!.removeFromVault(amount: amount)
420            } else if assetType == AssetType.stflow {
421                self.userPositions[user]!.removeStFlowDeposit(amount: amount)
422                self.assetBalances[AssetType.stflow]!.removeFromVault(amount: amount)
423            }
424            
425            self.userPositions[user]!.removeShares(amount: shares)
426            
427            VaultCore.totalShares = VaultCore.totalShares - shares
428            VaultCore.totalPrincipal = VaultCore.totalPrincipal - amount
429            VaultCore.totalValueLocked = VaultCore.totalValueLocked - amount
430            
431            emit WithdrawalProcessed(user: user, requestId: requestId, amount: amount)
432            
433            // Return appropriate vault
434            if assetType == AssetType.flow {
435                return <- self.flowVault.withdraw(amount: amount)
436            } else {
437                return <- self.stFlowVault.withdraw(amount: amount)
438            }
439        }
440        
441        // ====================================================================
442        // STRATEGY FUNCTIONS (AGENT ONLY)
443        // ====================================================================
444        access(all) fun withdrawForStrategy(assetType: AssetType, amount: UFix64): @{FungibleToken.Vault} {
445            pre {
446                amount > 0.0: "Amount must be positive"
447            }
448            
449            self.assetBalances[assetType]!.moveToStrategy(amount: amount)
450            
451            if assetType == AssetType.flow {
452                return <- self.flowVault.withdraw(amount: amount)
453            } else {
454                return <- self.stFlowVault.withdraw(amount: amount)
455            }
456        }
457        
458        access(all) fun depositFromStrategy(assetType: AssetType, from: @{FungibleToken.Vault}) {
459            let amount = from.balance
460            
461            self.assetBalances[assetType]!.moveFromStrategy(amount: amount)
462            
463            if assetType == AssetType.flow {
464                self.flowVault.deposit(from: <-from as! @FlowToken.Vault)
465            } else {
466                self.stFlowVault.deposit(from: <-from as! @stFlowToken.Vault)
467            }
468        }
469        
470        access(all) fun recordYieldHarvest(assetType: AssetType, amount: UFix64) {
471            self.assetBalances[assetType]!.recordHarvest(amount: amount)
472            VaultCore.totalYieldGenerated = VaultCore.totalYieldGenerated + amount
473        }
474        
475        // ====================================================================
476        // BRIDGING FUNCTIONS (AGENT ONLY)
477        // ====================================================================
478        access(all) fun withdrawForEVMBridge(assetType: AssetType, amount: UFix64): @{FungibleToken.Vault} {
479            pre {
480                assetType == AssetType.flow: "Only FLOW can be bridged"
481                amount > 0.0: "Amount must be positive"
482            }
483            
484            self.assetBalances[assetType]!.moveToEVM(amount: amount)
485            VaultCore.totalBridgedToEVM = VaultCore.totalBridgedToEVM + amount
486            
487            emit BridgedToEVM(asset: "FLOW", amount: amount)
488            
489            return <- self.flowVault.withdraw(amount: amount)
490        }
491        
492        access(all) fun depositFromEVMBridge(from: @FlowToken.Vault) {
493            let amount = from.balance
494            
495            self.assetBalances[AssetType.flow]!.moveFromEVM(amount: amount)
496            VaultCore.totalBridgedFromEVM = VaultCore.totalBridgedFromEVM + amount
497            
498            self.flowVault.deposit(from: <-from)
499            
500            emit BridgedFromEVM(asset: "FLOW", amount: amount)
501        }
502        
503        // ====================================================================
504        // ADMIN FUNCTIONS
505        // ====================================================================
506        access(all) fun whitelistStrategy(strategyName: String, status: Bool) {
507            self.whitelistedStrategies[strategyName] = status
508        }
509        
510        access(all) fun setUserYieldEligibility(user: Address, eligible: Bool) {
511            pre {
512                self.userPositions[user] != nil: "User not found"
513            }
514            
515            self.userPositions[user]!.setYieldEligible(eligible: eligible)
516            emit YieldEligibilityChanged(user: user, eligible: eligible)
517        }
518        
519        access(all) fun setUserVRFMultiplier(user: Address, multiplier: UFix64) {
520            pre {
521                self.userPositions[user] != nil: "User not found"
522                multiplier >= 1.0 && multiplier <= 100.0: "Invalid multiplier"
523            }
524            
525            self.userPositions[user]!.setVRFMultiplier(multiplier: multiplier)
526        }
527        
528        // ====================================================================
529        // VIEW FUNCTIONS
530        // ====================================================================
531        access(all) fun getUserPosition(user: Address): UserPosition? {
532            return self.userPositions[user]
533        }
534        
535        access(all) fun getAssetBalance(assetType: AssetType): AssetBalance {
536            return self.assetBalances[assetType]!
537        }
538        
539        access(all) fun getWithdrawalRequest(requestId: UInt64): WithdrawalRequest? {
540            return self.withdrawalRequests[requestId]
541        }
542        
543        access(all) fun calculateShares(assetType: AssetType, amount: UFix64): UFix64 {
544            if VaultCore.totalShares == 0.0 {
545                return amount
546            }
547            
548            let assetBalance = self.assetBalances[assetType]!
549            let totalAssetValue = assetBalance.totalBalance
550            
551            if totalAssetValue == 0.0 {
552                return amount
553            }
554            
555            return (amount * VaultCore.totalShares) / VaultCore.totalValueLocked
556        }
557        
558        access(all) fun isStrategyWhitelisted(strategyName: String): Bool {
559            return self.whitelistedStrategies[strategyName] ?? false
560        }
561    }
562    
563    // ====================================================================
564    // PUBLIC INTERFACE
565    // ====================================================================
566    access(all) resource interface VaultPublic {
567        access(all) fun getUserPosition(user: Address): UserPosition?
568        access(all) fun getAssetBalance(assetType: AssetType): AssetBalance
569        access(all) fun getWithdrawalRequest(requestId: UInt64): WithdrawalRequest?
570        access(all) fun getVaultMetrics(): VaultMetrics
571    }
572    
573    // ====================================================================
574    // ADMIN RESOURCE
575    // ====================================================================
576    access(all) resource Admin {
577        access(all) fun toggleDeposits(enabled: Bool) {
578            VaultCore.depositsEnabled = enabled
579        }
580        
581        access(all) fun toggleWithdrawals(enabled: Bool) {
582            VaultCore.withdrawalsEnabled = enabled
583        }
584        
585        access(all) fun setEmergencyMode(enabled: Bool) {
586            VaultCore.emergencyMode = enabled
587            if enabled {
588                VaultCore.depositsEnabled = false
589            }
590            emit EmergencyModeToggled(enabled: enabled)
591        }
592        
593        access(all) fun advanceEpoch() {
594            VaultCore.currentEpoch = VaultCore.currentEpoch + 1
595            VaultCore.lastEpochStart = getCurrentBlock().timestamp
596        }
597        
598        access(all) fun setEpochDuration(duration: UFix64) {
599            VaultCore.epochDuration = duration
600        }
601    }
602    
603    // ====================================================================
604    // CONTRACT FUNCTIONS
605    // ====================================================================
606    access(all) fun getVaultMetrics(): VaultMetrics {
607        return VaultMetrics(
608            tvl: self.totalValueLocked,
609            users: self.totalUsers,
610            shares: self.totalShares,
611            principal: self.totalPrincipal,
612            yieldGen: self.totalYieldGenerated,
613            bridgedTo: self.totalBridgedToEVM,
614            bridgedFrom: self.totalBridgedFromEVM,
615            epoch: self.currentEpoch,
616            deposits: self.depositsEnabled,
617            withdrawals: self.withdrawalsEnabled,
618            emergency: self.emergencyMode
619        )
620    }
621    
622    access(all) fun createEmptyVault(): @Vault {
623        return <- create Vault()
624    }
625    
626    // ====================================================================
627    // INITIALIZATION
628    // ====================================================================
629    init() {
630        self.VaultStoragePath = /storage/TrueMultiAssetVault
631        self.VaultPublicPath = /public/TrueMultiAssetVault
632        self.AdminStoragePath = /storage/VaultAdmin
633        self.AgentStoragePath = /storage/VaultAgent
634        
635        self.totalValueLocked = 0.0
636        self.totalUsers = 0
637        self.totalPrincipal = 0.0
638        self.totalYieldGenerated = 0.0
639        self.totalShares = 0.0
640        
641        self.currentEpoch = 1
642        self.lastEpochStart = getCurrentBlock().timestamp
643        self.epochDuration = 604800.0 // 7 days in seconds
644        
645        self.depositsEnabled = true
646        self.withdrawalsEnabled = true
647        self.emergencyMode = false
648        
649        self.totalBridgedToEVM = 0.0
650        self.totalBridgedFromEVM = 0.0
651        
652        self.nextRequestId = 0
653        
654        // Create admin resource
655        self.account.storage.save(<-create Admin(), to: self.AdminStoragePath)
656        
657        emit VaultInitialized()
658    }
659}