Smart Contract
VaultCore
A.79f5b5b0f95a160b.VaultCore
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}