Smart Contract
IncrementFarmingStrategy
A.79f5b5b0f95a160b.IncrementFarmingStrategy
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import stFlowToken from 0xd6f80565193ad727
4import Staking from 0x1b77ba4b414de352
5
6/// Increment Fi LP farming strategy
7/// Stake LP tokens in farming pools to earn rewards
8access(all) contract IncrementFarmingStrategy {
9
10 // ====================================================================
11 // EVENTS
12 // ====================================================================
13 access(all) event FarmDeposited(poolId: UInt64, amount: UFix64, tokenKey: String)
14 access(all) event FarmWithdrawn(poolId: UInt64, amount: UFix64)
15 access(all) event RewardsClaimed(poolId: UInt64, rewards: {String: UFix64})
16 access(all) event StrategyExecuted(poolId: UInt64, amount: UFix64)
17
18 // ====================================================================
19 // STATE
20 // ====================================================================
21 access(self) var totalDeposited: {UInt64: UFix64}
22 access(self) var totalRewardsClaimed: {String: UFix64}
23
24 // ====================================================================
25 // STRATEGY RESOURCE
26 // ====================================================================
27 access(all) resource Strategy {
28 access(self) let flowVault: @FlowToken.Vault
29 access(self) let stFlowVault: @stFlowToken.Vault
30 access(self) let activePositions: {UInt64: Bool}
31 access(self) let userCertificate: @Staking.UserCertificate
32
33 init() {
34 self.flowVault <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) as! @FlowToken.Vault
35 self.stFlowVault <- stFlowToken.createEmptyVault(vaultType: Type<@stFlowToken.Vault>()) as! @stFlowToken.Vault
36 self.activePositions = {}
37 self.userCertificate <- Staking.setupUser()
38 }
39
40 /// Execute farming: deposit into specified pool
41 access(all) fun executeStrategy(poolId: UInt64, from: @{FungibleToken.Vault}): Bool {
42 pre {
43 from.balance > 0.0: "Cannot deposit zero"
44 }
45
46 let amount = from.balance
47
48 // Get staking pool collection
49 let stakingCollection = getAccount(0x1b77ba4b414de352)
50 .capabilities
51 .borrow<&{Staking.PoolCollectionPublic}>(Staking.CollectionPublicPath)
52 ?? panic("Cannot access staking collection")
53
54 // Get specific pool
55 let poolRef = stakingCollection.getPool(pid: poolId)
56 let poolInfo = poolRef.getPoolInfo()
57
58 // Verify pool is active (status "1" = RUNNING)
59 assert(poolInfo.status == "1", message: "Pool is not active")
60
61 // Stake tokens - signature: stake(staker: Address, stakingToken: @{FungibleToken.Vault})
62 poolRef.stake(staker: self.owner!.address, stakingToken: <-from)
63
64 // Track position
65 self.activePositions[poolId] = true
66
67 if IncrementFarmingStrategy.totalDeposited[poolId] == nil {
68 IncrementFarmingStrategy.totalDeposited[poolId] = 0.0
69 }
70 IncrementFarmingStrategy.totalDeposited[poolId] = IncrementFarmingStrategy.totalDeposited[poolId]! + amount
71
72 emit FarmDeposited(poolId: poolId, amount: amount, tokenKey: poolInfo.acceptTokenKey)
73 emit StrategyExecuted(poolId: poolId, amount: amount)
74
75 return true
76 }
77
78 /// Harvest rewards from a farming pool
79 access(all) fun harvestPool(poolId: UInt64): @{String: {FungibleToken.Vault}} {
80 pre {
81 self.activePositions[poolId] ?? false: "No active position in this pool"
82 }
83
84 let stakingCollection = getAccount(0x1b77ba4b414de352)
85 .capabilities
86 .borrow<&{Staking.PoolCollectionPublic}>(Staking.CollectionPublicPath)
87 ?? panic("Cannot access staking collection")
88
89 let poolRef = stakingCollection.getPool(pid: poolId)
90
91 // Get user info to see unclaimed rewards
92 let userInfo = poolRef.getUserInfo(address: self.owner!.address)
93
94 if userInfo != nil {
95 let unclaimedRewards = userInfo!.unclaimedRewards
96
97 // Claim rewards - signature: claimRewards(userCertificate: &{IdentityCertificate})
98 let rewardVaults <- poolRef.claimRewards(
99 userCertificate: &self.userCertificate as &{Staking.IdentityCertificate}
100 )
101
102 // Track claimed rewards
103 for tokenKey in unclaimedRewards.keys {
104 let rewardAmount = unclaimedRewards[tokenKey]!
105
106 if IncrementFarmingStrategy.totalRewardsClaimed[tokenKey] == nil {
107 IncrementFarmingStrategy.totalRewardsClaimed[tokenKey] = 0.0
108 }
109 IncrementFarmingStrategy.totalRewardsClaimed[tokenKey] =
110 IncrementFarmingStrategy.totalRewardsClaimed[tokenKey]! + rewardAmount
111 }
112
113 emit RewardsClaimed(poolId: poolId, rewards: unclaimedRewards)
114
115 return <- rewardVaults
116 } else {
117 // Return empty dictionary if no user info
118 let emptyVaults: @{String: {FungibleToken.Vault}} <- {}
119 return <- emptyVaults
120 }
121 }
122
123 /// Harvest all active positions
124 access(all) fun harvestAll(): @{String: {FungibleToken.Vault}} {
125 var allRewards: @{String: {FungibleToken.Vault}} <- {}
126
127 for poolId in self.activePositions.keys {
128 if self.activePositions[poolId]! {
129 let harvested <- self.harvestPool(poolId: poolId)
130
131 // Merge harvested vaults into allRewards
132 for tokenKey in harvested.keys {
133 let rewardVault <- harvested.remove(key: tokenKey)!
134
135 if allRewards.containsKey(tokenKey) {
136 let existingVault = &allRewards[tokenKey] as &{FungibleToken.Vault}?
137 existingVault!.deposit(from: <-rewardVault)
138 } else {
139 allRewards[tokenKey] <-! rewardVault
140 }
141 }
142
143 destroy harvested
144 }
145 }
146
147 return <- allRewards
148 }
149
150 /// Withdraw from a farming pool
151 access(all) fun withdrawFromPool(poolId: UInt64, amount: UFix64): @{FungibleToken.Vault} {
152 pre {
153 self.activePositions[poolId] ?? false: "No active position in this pool"
154 amount > 0.0: "Amount must be positive"
155 }
156
157 let stakingCollection = getAccount(0x1b77ba4b414de352)
158 .capabilities
159 .borrow<&{Staking.PoolCollectionPublic}>(Staking.CollectionPublicPath)
160 ?? panic("Cannot access staking collection")
161
162 let poolRef = stakingCollection.getPool(pid: poolId)
163
164 // Unstake tokens - signature: unstake(userCertificate: &{IdentityCertificate}, amount: UFix64)
165 let withdrawnVault <- poolRef.unstake(
166 userCertificate: &self.userCertificate as &{Staking.IdentityCertificate},
167 amount: amount
168 )
169
170 IncrementFarmingStrategy.totalDeposited[poolId] = IncrementFarmingStrategy.totalDeposited[poolId]! - amount
171
172 if IncrementFarmingStrategy.totalDeposited[poolId]! <= 0.0 {
173 self.activePositions[poolId] = false
174 }
175
176 emit FarmWithdrawn(poolId: poolId, amount: amount)
177
178 return <- withdrawnVault
179 }
180
181 /// Emergency exit - withdraw all from all pools
182 access(all) fun emergencyExit(): @{String: {FungibleToken.Vault}} {
183 var allWithdrawn: @{String: {FungibleToken.Vault}} <- {}
184
185 let stakingCollection = getAccount(0x1b77ba4b414de352)
186 .capabilities
187 .borrow<&{Staking.PoolCollectionPublic}>(Staking.CollectionPublicPath)
188 ?? panic("Cannot access staking collection")
189
190 for poolId in self.activePositions.keys {
191 if self.activePositions[poolId]! {
192 let poolRef = stakingCollection.getPool(pid: poolId)
193 let userInfo = poolRef.getUserInfo(address: self.owner!.address)
194
195 if userInfo != nil && userInfo!.stakingAmount > 0.0 {
196 let withdrawn <- poolRef.unstake(
197 userCertificate: &self.userCertificate as &{Staking.IdentityCertificate},
198 amount: userInfo!.stakingAmount
199 )
200
201 let tokenKey = poolRef.getPoolInfo().acceptTokenKey
202
203 if allWithdrawn.containsKey(tokenKey) {
204 let existingVault = &allWithdrawn[tokenKey] as &{FungibleToken.Vault}?
205 existingVault!.deposit(from: <-withdrawn)
206 } else {
207 allWithdrawn[tokenKey] <-! withdrawn
208 }
209
210 self.activePositions[poolId] = false
211 }
212 }
213 }
214
215 return <- allWithdrawn
216 }
217
218 /// Get positions info
219 access(all) fun getPositions(): {UInt64: {String: UFix64}} {
220 let positions: {UInt64: {String: UFix64}} = {}
221
222 let stakingCollection = getAccount(0x1b77ba4b414de352)
223 .capabilities
224 .borrow<&{Staking.PoolCollectionPublic}>(Staking.CollectionPublicPath)
225 ?? panic("Cannot access staking collection")
226
227 for poolId in self.activePositions.keys {
228 if self.activePositions[poolId]! {
229 let poolRef = stakingCollection.getPool(pid: poolId)
230 let userInfo = poolRef.getUserInfo(address: self.owner!.address)
231
232 if userInfo != nil {
233 positions[poolId] = {
234 "stakingAmount": userInfo!.stakingAmount,
235 "isBlocked": userInfo!.isBlocked ? 1.0 : 0.0
236 }
237 }
238 }
239 }
240
241 return positions
242 }
243
244 access(all) fun getBalances(): {String: UFix64} {
245 return {
246 "flow": self.flowVault.balance,
247 "stflow": self.stFlowVault.balance
248 }
249 }
250 }
251
252 // ====================================================================
253 // CONTRACT FUNCTIONS
254 // ====================================================================
255 access(all) fun createStrategy(): @Strategy {
256 return <- create Strategy()
257 }
258
259 access(all) fun getMetrics(): {String: AnyStruct} {
260 return {
261 "totalDepositedByPool": self.totalDeposited,
262 "totalRewardsClaimed": self.totalRewardsClaimed
263 }
264 }
265
266 /// Query available farming pools
267 access(all) fun getActivePools(): [{String: AnyStruct}] {
268 let pools: [{String: AnyStruct}] = []
269
270 let stakingCollection = getAccount(0x1b77ba4b414de352)
271 .capabilities
272 .borrow<&{Staking.PoolCollectionPublic}>(Staking.CollectionPublicPath)
273
274 if stakingCollection != nil {
275 let poolCount = stakingCollection!.getCollectionLength()
276 var i: UInt64 = 0
277
278 while i < UInt64(poolCount) && i < 20 {
279 let poolRef = stakingCollection!.getPool(pid: i)
280 let poolInfo = poolRef.getPoolInfo()
281
282 if poolInfo.status == "1" {
283 pools.append({
284 "pid": poolInfo.pid,
285 "acceptTokenKey": poolInfo.acceptTokenKey,
286 "totalStaking": poolInfo.totalStaking,
287 "limitAmount": poolInfo.limitAmount,
288 "status": poolInfo.status
289 })
290 }
291
292 i = i + 1
293 }
294 }
295
296 return pools
297 }
298
299 // ====================================================================
300 // INITIALIZATION
301 // ====================================================================
302 init() {
303 self.totalDeposited = {}
304 self.totalRewardsClaimed = {}
305 }
306}