Smart Contract
FlowYieldVaultsConnectorV2
A.a092c4aab33daeda.FlowYieldVaultsConnectorV2
1/*
2FlowYieldVaultsConnectorV2 - Mainnet FlowYieldVaults Integration (Secured)
3
4This connector enables PrizeLinkedAccounts to deposit funds into FlowYieldVaults (yield-bearing strategies)
5and implements DeFiActions.Sink and DeFiActions.Source interfaces.
6
7Security model:
8- The YieldVaultManagerWrapper resource is stored in the deployer's account
9- A PUBLIC capability exposes only getYieldVaultBalance() for read-only queries
10- An ENTITLED capability (auth(Operate)) gates depositToYieldVault/withdrawFromYieldVault
11- The Connector struct holds the entitled capability internally, so only the contract
12 that stores the Connector (PrizeLinkedAccounts) can trigger deposit/withdraw operations
13
14FlowYieldVaults Contract: mainnet://b1d63873c3cc9f79.FlowYieldVaults
15*/
16
17import FungibleToken from 0xf233dcee88fe0abe
18import FlowYieldVaults from 0xb1d63873c3cc9f79
19import FlowYieldVaultsClosedBeta from 0xb1d63873c3cc9f79
20import DeFiActions from 0x92195d814edf9cb0
21
22access(all) contract FlowYieldVaultsConnectorV2 {
23
24 /// Entitlement required to deposit/withdraw through the YieldVaultManagerWrapper.
25 /// Only the Connector struct (living inside PrizeLinkedAccounts' Pool) holds a capability with this entitlement.
26 access(all) entitlement Operate
27
28 // Storage paths
29 access(all) let ManagerStoragePath: StoragePath
30 access(all) let ManagerPublicPath: PublicPath
31
32 // Events
33 access(all) event ConnectorCreated(managerAddress: Address, strategyType: String, vaultType: String)
34 access(all) event DepositedToYieldVault(yieldVaultID: UInt64, amount: UFix64, vaultType: String)
35 access(all) event WithdrawnFromYieldVault(yieldVaultID: UInt64, amount: UFix64, vaultType: String)
36 access(all) event YieldVaultCreated(yieldVaultID: UInt64, strategyType: String, initialAmount: UFix64)
37
38 /// YieldVaultManagerWrapper Resource
39 /// Wraps FlowYieldVaults.YieldVaultManager with beta badge authentication.
40 ///
41 /// - getYieldVaultBalance() is access(all) — callable via the public capability for read-only queries
42 /// - depositToYieldVault() and withdrawFromYieldVault() require the Operate entitlement
43 access(all) resource YieldVaultManagerWrapper {
44 access(self) let yieldVaultManagerCap: Capability<auth(FungibleToken.Withdraw) &FlowYieldVaults.YieldVaultManager>
45 access(self) let betaBadgeCap: Capability<auth(FlowYieldVaultsClosedBeta.Beta) &FlowYieldVaultsClosedBeta.BetaBadge>
46 access(self) var yieldVaultID: UInt64?
47 access(all) let vaultType: Type
48 access(all) let strategyType: Type
49
50 init(
51 yieldVaultManagerCap: Capability<auth(FungibleToken.Withdraw) &FlowYieldVaults.YieldVaultManager>,
52 betaBadgeCap: Capability<auth(FlowYieldVaultsClosedBeta.Beta) &FlowYieldVaultsClosedBeta.BetaBadge>,
53 vaultType: Type,
54 strategyType: Type
55 ) {
56 pre {
57 yieldVaultManagerCap.check(): "Invalid YieldVaultManager capability"
58 betaBadgeCap.check(): "Invalid Beta badge capability"
59 }
60
61 self.yieldVaultManagerCap = yieldVaultManagerCap
62 self.betaBadgeCap = betaBadgeCap
63 self.vaultType = vaultType
64 self.strategyType = strategyType
65 self.yieldVaultID = nil
66 }
67
68 /// Deposit tokens into the yield vault. Requires Operate entitlement.
69 access(Operate) fun depositToYieldVault(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
70 pre {
71 from.getType() == self.vaultType: "Vault type mismatch"
72 from.balance > 0.0: "Cannot deposit zero balance"
73 }
74
75 let amount = from.balance
76 let yieldVaultManager = self.yieldVaultManagerCap.borrow()
77 ?? panic("Cannot borrow YieldVaultManager")
78 let betaBadge = self.betaBadgeCap.borrow()
79 ?? panic("Cannot borrow Beta badge")
80
81 // If we don't have a YieldVault yet, create one
82 if self.yieldVaultID == nil {
83 let initialVault <- from.withdraw(amount: amount)
84
85 let newID = yieldVaultManager.createYieldVault(
86 betaRef: betaBadge,
87 strategyType: self.strategyType,
88 withVault: <- initialVault
89 )
90
91 self.yieldVaultID = newID
92
93 emit YieldVaultCreated(
94 yieldVaultID: self.yieldVaultID!,
95 strategyType: self.strategyType.identifier,
96 initialAmount: amount
97 )
98
99 emit DepositedToYieldVault(
100 yieldVaultID: self.yieldVaultID!,
101 amount: amount,
102 vaultType: self.vaultType.identifier
103 )
104 } else {
105 let depositVault <- from.withdraw(amount: amount)
106
107 yieldVaultManager.depositToYieldVault(
108 betaRef: betaBadge,
109 self.yieldVaultID!,
110 from: <- depositVault
111 )
112
113 emit DepositedToYieldVault(
114 yieldVaultID: self.yieldVaultID!,
115 amount: amount,
116 vaultType: self.vaultType.identifier
117 )
118 }
119 }
120
121 /// Query the current balance in the yield vault. Publicly accessible (read-only).
122 access(all) fun getYieldVaultBalance(): UFix64 {
123 if self.yieldVaultID == nil {
124 return 0.0
125 }
126
127 let yieldVaultManager = self.yieldVaultManagerCap.borrow()
128 ?? panic("Cannot borrow YieldVaultManager")
129
130 let yieldVaultRef = yieldVaultManager.borrowYieldVault(id: self.yieldVaultID!)
131 if yieldVaultRef == nil {
132 return 0.0
133 }
134
135 return yieldVaultRef!.getYieldVaultBalance()
136 }
137
138 /// Withdraw tokens from the yield vault. Requires Operate entitlement.
139 access(Operate) fun withdrawFromYieldVault(maxAmount: UFix64): @{FungibleToken.Vault} {
140 pre {
141 self.yieldVaultID != nil: "No YieldVault initialized"
142 maxAmount > 0.0: "Cannot withdraw zero amount"
143 }
144
145 let yieldVaultManager = self.yieldVaultManagerCap.borrow()
146 ?? panic("Cannot borrow YieldVaultManager")
147
148 let available = self.getYieldVaultBalance()
149 let withdrawAmount = maxAmount < available ? maxAmount : available
150
151 assert(withdrawAmount > 0.0, message: "Insufficient balance in YieldVault")
152
153 let vault <- yieldVaultManager.withdrawFromYieldVault(self.yieldVaultID!, amount: withdrawAmount)
154
155 emit WithdrawnFromYieldVault(
156 yieldVaultID: self.yieldVaultID!,
157 amount: withdrawAmount,
158 vaultType: vault.getType().identifier
159 )
160
161 return <- vault
162 }
163 }
164
165 /// Connector Struct
166 /// Implements DeFiActions.Sink and DeFiActions.Source.
167 ///
168 /// Holds an entitled capability (auth(Operate)) to the YieldVaultManagerWrapper for deposit/withdraw,
169 /// and uses the public capability for balance queries.
170 /// Once this struct is stored inside PrizeLinkedAccounts' Pool (access(contract) field),
171 /// only PrizeLinkedAccounts can trigger deposit/withdraw operations.
172 access(all) struct Connector: DeFiActions.Sink, DeFiActions.Source {
173 access(all) let managerAddress: Address
174 access(self) let operateCap: Capability<auth(Operate) &YieldVaultManagerWrapper>
175 access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
176 access(all) let vaultType: Type
177
178 init(
179 managerAddress: Address,
180 operateCap: Capability<auth(Operate) &YieldVaultManagerWrapper>,
181 vaultType: Type
182 ) {
183 pre {
184 operateCap.check(): "Invalid Operate capability for YieldVaultManagerWrapper"
185 }
186 self.managerAddress = managerAddress
187 self.operateCap = operateCap
188 self.vaultType = vaultType
189 self.uniqueID = nil
190 }
191
192 /// DeFiActions.Sink Implementation — deposits through the entitled capability
193 access(all) fun depositCapacity(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
194 let managerRef = self.operateCap.borrow()
195 ?? panic("Cannot borrow YieldVaultManagerWrapper via Operate capability")
196
197 managerRef.depositToYieldVault(from: from)
198 }
199
200 access(all) view fun getSinkType(): Type {
201 return self.vaultType
202 }
203
204 access(all) view fun minimumCapacity(): UFix64 {
205 return 0.0
206 }
207
208 /// DeFiActions.Source Implementation — balance via public path, withdraw via entitled capability
209 access(all) fun minimumAvailable(): UFix64 {
210 let managerAccount = getAccount(self.managerAddress)
211 if let managerRef = managerAccount.capabilities.borrow<&YieldVaultManagerWrapper>(
212 FlowYieldVaultsConnectorV2.ManagerPublicPath
213 ) {
214 return managerRef.getYieldVaultBalance()
215 }
216 return 0.0
217 }
218
219 access(FungibleToken.Withdraw) fun withdrawAvailable(maxAmount: UFix64): @{FungibleToken.Vault} {
220 let managerRef = self.operateCap.borrow()
221 ?? panic("Cannot borrow YieldVaultManagerWrapper via Operate capability")
222
223 return <- managerRef.withdrawFromYieldVault(maxAmount: maxAmount)
224 }
225
226 access(all) view fun getSourceType(): Type {
227 return self.vaultType
228 }
229
230 /// DeFiActions Component Info
231 access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
232 return DeFiActions.ComponentInfo(
233 type: self.getType(),
234 id: self.uniqueID?.id,
235 innerComponents: []
236 )
237 }
238
239 access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
240 return self.uniqueID
241 }
242
243 access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
244 self.uniqueID = id
245 }
246 }
247
248 /// Create a new YieldVaultManagerWrapper and store it.
249 /// Returns a Connector struct that holds an entitled capability for deposit/withdraw.
250 /// A read-only public capability is also published for balance queries.
251 access(all) fun createConnectorAndManager(
252 account: auth(Storage, Capabilities) &Account,
253 yieldVaultManagerCap: Capability<auth(FungibleToken.Withdraw) &FlowYieldVaults.YieldVaultManager>,
254 betaBadgeCap: Capability<auth(FlowYieldVaultsClosedBeta.Beta) &FlowYieldVaultsClosedBeta.BetaBadge>,
255 vaultType: Type,
256 strategyType: Type
257 ): Connector {
258 // Validate that the strategy supports the vault type
259 let supportedVaults = FlowYieldVaults.getSupportedInitializationVaults(forStrategy: strategyType)
260 assert(
261 supportedVaults[vaultType] == true,
262 message: "Strategy does not support vault type"
263 )
264
265 // Create and store the YieldVaultManagerWrapper resource
266 let manager <- create YieldVaultManagerWrapper(
267 yieldVaultManagerCap: yieldVaultManagerCap,
268 betaBadgeCap: betaBadgeCap,
269 vaultType: vaultType,
270 strategyType: strategyType
271 )
272
273 account.storage.save(<-manager, to: self.ManagerStoragePath)
274
275 // Issue entitled capability for the Connector (NOT published — only the Connector holds it)
276 let operateCap = account.capabilities.storage.issue<auth(Operate) &YieldVaultManagerWrapper>(self.ManagerStoragePath)
277
278 // Publish read-only capability for public balance queries
279 let publicCap = account.capabilities.storage.issue<&YieldVaultManagerWrapper>(self.ManagerStoragePath)
280 account.capabilities.publish(publicCap, at: self.ManagerPublicPath)
281
282 emit ConnectorCreated(
283 managerAddress: account.address,
284 strategyType: strategyType.identifier,
285 vaultType: vaultType.identifier
286 )
287
288 // Return the struct connector that holds the entitled capability
289 return Connector(
290 managerAddress: account.address,
291 operateCap: operateCap,
292 vaultType: vaultType
293 )
294 }
295
296 init() {
297 let identifier = "flowYieldVaultsManagerV2_\(self.account.address)"
298 self.ManagerStoragePath = StoragePath(identifier: identifier)!
299 self.ManagerPublicPath = PublicPath(identifier: identifier)!
300 }
301}
302